ByteAid.Tools.Ado 1.0.0-alpha.2

This is a prerelease version of ByteAid.Tools.Ado.
dotnet add package ByteAid.Tools.Ado --version 1.0.0-alpha.2
                    
NuGet\Install-Package ByteAid.Tools.Ado -Version 1.0.0-alpha.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ByteAid.Tools.Ado" Version="1.0.0-alpha.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ByteAid.Tools.Ado" Version="1.0.0-alpha.2" />
                    
Directory.Packages.props
<PackageReference Include="ByteAid.Tools.Ado" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ByteAid.Tools.Ado --version 1.0.0-alpha.2
                    
#r "nuget: ByteAid.Tools.Ado, 1.0.0-alpha.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ByteAid.Tools.Ado@1.0.0-alpha.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ByteAid.Tools.Ado&version=1.0.0-alpha.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ByteAid.Tools.Ado&version=1.0.0-alpha.2&prerelease
                    
Install as a Cake Tool

ByteAid.Tools.Ado

bta-ado is a general-purpose CLI for Azure DevOps. It authenticates with a work/school account (Entra ID) via the device code flow — no PATs — and caches tokens in the OS-native encrypted store so subsequent runs renew silently.

The surface is organized into command areas, one per Azure DevOps domain. Work items is the first area implemented; boards, repos, pipelines, and artifacts are the intended trajectory. Each new area plugs into the same hosting, authentication, and input-resolution pipeline described below.

Packages

Package Contents
ByteAid.Tools.Ado.Cli The bta-ado global tool (command tree, hosting, input resolution).
ByteAid.Tools.Ado Reusable core library: token provider + per-area services. Consumable independently of the CLI.

Both inherit a single <Version> from Directory.Build.props. Target framework: .NET 10.


Installation

dotnet tool install --global ByteAid.Tools.Ado.Cli   # install
dotnet tool update  --global ByteAid.Tools.Ado.Cli   # update

Requires the .NET 10 SDK. After install, bta-ado is on the PATH.


Authentication

Auth is centralized in AzureDevOpsTokenProvider (core library) and shared across every area.

  • Flow: Entra ID device code against a multi-tenant ByteAid app registration. First run prints a https://microsoft.com/devicelogin URL + code; after sign-in the access token (~1h) and refresh token are cached.
  • Resource: tokens are minted for the Azure DevOps first-party resource (499b84ac-1321-427f-aa17-267ca6975798/.default), constant across tenants/orgs.
  • Cache: persisted via Microsoft.Identity.Client.Extensions.Msal to the OS-native encrypted store — %APPDATA%/b8/adocli (Windows DPAPI), Keychain (macOS), Keyring (Linux). The MSAL cache is multi-account: it can hold tokens for N signed-in accounts simultaneously. Silent acquisition reuses the refresh token; only an expired/absent refresh token falls back to device code.
  • Account selection: with several accounts cached, --account <username> picks which one to use. Omitted, the first cached account is used. No cached match triggers a fresh device-code sign-in.
  • Tenant targeting: --tenant-id <GUID> directs the token request at the tenant backing the target org. Omitted, the request goes to the caller's home tenant via /organizations.

App registration prerequisite (one-time). The app must have "Allow public client flows" enabled in Entra ID, otherwise the device code request is rejected.

Headless / WSL2. If no secure store is available, the provider falls back to an in-memory cache (re-auth each run) and logs a warning instead of failing.


Command structure

bta-ado
└── <verb> <area> [options]        verbs: show | create | update | delete

Work items area:

bta-ado show   workitems      List work items in a project (WIQL + batch fetch; type/state/tag/where filters)
bta-ado show   workitem        Show one item by id with its relations (parent + children)
bta-ado show   workitemtypes   List the project's valid types; infer Agile vs Scrum
bta-ado create workitem        Create a single work item, optionally parented and tagged
bta-ado create tree            Create or idempotently --upsert a work item hierarchy from a JSON spec
bta-ado update workitem        Patch fields, re-parent, merge tags on an existing work item
bta-ado delete workitem        Delete work item(s), optionally recursive/destructive

Root options (all commands): --version, -h|--help.

Options common to every work items subcommand:

Option Alias Required Description
--organization -o yes Azure DevOps organization name
--project -p yes Project name
--tenant-id no Tenant backing the org (omit for your home tenant)
--account no Account username to use when several are signed in (omit = first cached)
--json no Emit the stable machine-readable JSON contract on stdout instead of the human view

All commands hit the Azure DevOps REST API at api-version 7.1.

Output & exit codes

  • stdout carries only data (the table or the --json payload); all diagnostics go to stderr, so a --json payload is always clean to pipe.
  • Exit 0 on success; exit 2 on any gate-worthy failure (auth, API error, bad input, type mismatch). An idempotent no-op (re-applying the same parent, removing an absent parent) still exits 0.

The --json contract

Stable, camelCase, indented; every key is always present (nulls emitted). A single work item:

{
  "id": 1234, "type": "Bug", "state": "Active", "title": "Login fails on Safari",
  "tags": ["bx:BG-009", "frontend"],
  "fields": { "Microsoft.VSTS.Common.Priority": "1" },
  "relations": {
    "parent": { "id": 1200, "rel": "System.LinkTypes.Hierarchy-Reverse" },
    "children": [ { "id": 1250, "rel": "System.LinkTypes.Hierarchy-Forward" } ]
  }
}

tags is System.Tags split into entries; fields carries only the --field reference names requested (identity fields collapse to display name); relations.parent is null for an orphan. show workitems wraps the list as { project, count, workItems: [...] }; show workitemtypes returns { process, project, types: [...] }; create tree returns { project, count, items: [{ key, id, type, title, parentId, action }] } with action ∈ created|updated|unchanged.


Commands

show workitems

Runs a WIQL query (SELECT [System.Id] FROM WorkItems ...), then batch-fetches fields (200 ids per request) and renders a table.

Option Alias Required Description
--organization -o yes Organization
--project -p yes Project
--type -t no Filter by type (Bug, "User Story", Task, …)
--state -s no Filter by state (Active, Closed, …)
--tag no Filter by an exact tag via WIQL CONTAINS (repeatable, AND-combined)
--where no Raw WIQL boolean clause appended to the WHERE with AND
--field no Extra field reference name surfaced in JSON output (repeatable)
--json no Emit JSON instead of the table
--tenant-id no Org tenant
--account no Account to use when several are signed in
--top no Max work items to return
bta-ado show workitems -o myOrg -p myProject
bta-ado show workitems -o myOrg -p myProject --type "User Story" --state Active --top 50
bta-ado show workitems -o myOrg -p myProject --tag "bx:FT-001" \
  --field Microsoft.VSTS.Common.Priority --json
bta-ado show workitems -o myOrg -p myProject --where "[Microsoft.VSTS.Common.Priority] = 1"

WIQL tag matching is whole-tag, not substring — [System.Tags] CONTAINS 'x' matches the tag x exactly, and WIQL has no tag prefix/wildcard. --tag "bx:" matches nothing; use the full --tag "bx:FT-001". To pull many correlated items in one query, OR the exact tags via --where ([System.Tags] CONTAINS 'bx:FT-001' OR …) or keep a constant marker tag on every managed item.


show workitem

Fetches one item by id with its relations ($expand=relations). --relations renders the parent/children tree in the human view; relations are always present in --json. Use it to detect orphans (relations.parent == null) or verify hierarchy.

Option Alias Required Description
--organization -o yes Organization
--project -p yes Project
--id yes Work item id to show
--relations no Render the parent/children tree (always in --json)
--field no Extra field reference name surfaced in JSON (repeatable)
--json no Emit JSON instead of the human view
--tenant-id no Org tenant
--account no Account to use when several are signed in
bta-ado show workitem -o myOrg -p myProject --id 1234 --relations
bta-ado show workitem -o myOrg -p myProject --id 1234 --json

show workitemtypes

Lists the work item types the project's process defines and infers the process family (Agile, Scrum, CMMI, Basic, Unknown) — useful before authoring a tree spec.

Option Alias Required Description
--organization -o yes Organization
--project -p yes Project
--json no Emit JSON ({ process, project, types })
--tenant-id no Org tenant
--account no Account to use when several are signed in
bta-ado show workitemtypes -o myOrg -p myProject
bta-ado show workitemtypes -o myOrg -p myProject --json

create workitem

Creates a single work item via a JSON Patch document, optionally linking it under a parent (System.LinkTypes.Hierarchy-Reverse).

Option Alias Required Description
--organization -o yes Organization
--project -p yes Project
--type -t yes Type (Epic, Feature, "User Story", Task, …)
--title yes Work item title
--description -d no Description (System.Description)
--parent no Parent work item id to link beneath
--tag no Tag to stamp on the new item (repeatable; e.g. bx:FT-001)
--json no Emit the created item as JSON
--tenant-id no Org tenant
--account no Account to use when several are signed in
bta-ado create workitem -o myOrg -p myProject -t Feature --title "Digital check-in" --tag bx:FT-001
bta-ado create workitem -o myOrg -p myProject -t Task --title "Validate document" \
  -d "Scan and validate the ID document" --parent 1234 --json

Prints the assigned id: Created Feature #1234: Digital check-in.


create tree

Creates a whole hierarchy from a JSON spec; each node is created and linked under its parent recursively. Referenced types are validated against the project's process up front, so a process mismatch (e.g. Scrum vs Agile) fails with one clear error instead of a partial tree.

Option Alias Required Description
--organization -o yes Organization
--project -p yes Project
--file -f yes Path to the JSON tree spec
--upsert no Idempotent: match nodes by their <prefix><key> correlation tag and update in place instead of duplicating
--tag-prefix no Prefix for a node's key correlation tag (default bx:)
--json no Emit the per-node result list as JSON
--tenant-id no Org tenant
--account no Account to use when several are signed in
bta-ado create tree -o myOrg -p myProject -f ./samples/guest-app-tree.json
bta-ado create tree -o myOrg -p myProject -f ./backlog.json --upsert --json

Spec format. The root requires type and title; description, key, and children are optional and nest recursively. Comments and trailing commas are tolerated. See samples/guest-app-tree.json.

A node's optional key is its stable correlation id. On create it is stamped as the tag <prefix><key> (e.g. bx:FT-001) so the item is correlatable on the next push. With --upsert, a node whose correlation tag already exists in the project is updated in place (title, description, re-parented if drifted) instead of duplicated — making the push idempotent.

⚠️ Upsert matching reads ADO's WIQL tag index, which lags a few seconds behind writes. Re-running --upsert immediately after a create may not see the just-created items and can still duplicate them. Leave a short gap between successive pushes.

{
  "type": "Epic", "title": "Guest mobile app", "key": "EP-001",
  "children": [
    {
      "type": "Feature", "title": "Digital check-in and mobile key", "key": "FT-001",
      "children": [
        {
          "type": "User Story", "title": "As a guest I want to check in from the app", "key": "US-001",
          "children": [
            { "type": "Task", "title": "Data capture form", "key": "TK-001" },
            { "type": "Task", "title": "ID document validation", "key": "TK-002" }
          ]
        }
      ]
    }
  ]
}

update workitem

Patches fields on an existing work item, and manages its parent link and tags. Omitted options leave state untouched. Sending zero changes is an error — but an idempotent op that resolves to a no-op (re-applying the same parent, removing an absent parent) succeeds (exit 0).

Option Alias Required Description
--organization -o yes Organization
--project -p yes Project
--id yes Work item id to edit
--title no New title
--description -d no New description
--state -s no New state (Active, Closed, …)
--field no Raw field update Reference.Name=value (repeatable)
--parent no Re-parent under this id (idempotent; replaces any existing parent)
--remove-parent no Drop the parent link (mutually exclusive with --parent)
--add-tag no Tag to merge into System.Tags without clobbering (repeatable)
--remove-tag no Tag to remove from System.Tags (repeatable)
--json no Emit the updated item as JSON
--tenant-id no Org tenant
--account no Account to use when several are signed in
bta-ado update workitem -o myOrg -p myProject --id 1234 --state Active
bta-ado update workitem -o myOrg -p myProject --id 9 --parent 1234        # re-parent an orphan Bug under its Feature
bta-ado update workitem -o myOrg -p myProject --id 1234 --add-tag bx:FT-001 --add-tag frontend
bta-ado update workitem -o myOrg -p myProject --id 1234 \
  --title "New title" \
  --field System.AssignedTo=user@org.com \
  --field Microsoft.VSTS.Common.Priority=1

--field takes the Azure DevOps reference name and value separated by =, and is repeatable to set multiple fields in one call. --parent re-parents via System.LinkTypes.Hierarchy-Reverse (removing any prior parent first); --add-tag/--remove-tag merge into the existing tag set, whereas --field System.Tags="a; b" replaces it.


delete workitem

Deletes one or more work items. Default sends them to the recycle bin (recoverable). --recursive expands each id into a post-order delete (children before parents); --destroy permanently removes.

Option Alias Required Description
--organization -o yes Organization
--project -p yes Project
--id yes Id(s) to delete (accepts multiple)
--recursive -r no Also delete all descendants (children first)
--destroy no Permanently destroy instead of recycle bin (IRREVERSIBLE)
--json no Emit the deleted id list as JSON
--tenant-id no Org tenant
--account no Account to use when several are signed in
bta-ado delete workitem -o myOrg -p myProject --id 1234
bta-ado delete workitem -o myOrg -p myProject --id 1234 5678 9012
bta-ado delete workitem -o myOrg -p myProject --id 1234 --recursive
bta-ado delete workitem -o myOrg -p myProject --id 1234 --destroy   # cannot be undone

Input resolution

Any option not provided on the command line is resolved from two fallback sources, in descending precedence:

command line > BTA_ADO_ARGS_* (environment) > .bta/ado.json (directory)

Resolution is centralized in EnvironmentInputs.AugmentArgs, which augments the parsed args before invocation. --help/--version short-circuit augmentation.

Environment variables (BTA_ADO_ARGS_*)

Any option maps to BTA_ADO_ARGS_{OPTION} (dashes → underscores, case-insensitive). The command line always wins over the environment.

export BTA_ADO_ARGS_ORGANIZATION=myOrg
export BTA_ADO_ARGS_PROJECT=myProject
export BTA_ADO_ARGS_TENANT_ID=00000000-0000-0000-0000-000000000000

bta-ado show workitems

Directory memory (.bta/ado.json)

To pin a repo's context (org, project, tenant, which account) without exporting variables or repeating flags, drop a .bta/ado.json. Discovery walks up from the current directory (like git locating .git): the nearest file wins and inherits keys it doesn't set from ancestors. .bta/ is the ByteAid umbrella folder — each tool reads its own .bta/<tool>.json sibling under the same discovery rule.

// <repo>/.bta/ado.json
{
  "organization": "myOrg",
  "project": "myProject",
  "tenant-id": "00000000-0000-0000-0000-000000000000",
  "account": "user@contoso.com"   // which cached account to use
}

Keys are the option long names (organization, tenant-id, account, …). A malformed or unreadable file is ignored without aborting the command.

appsettings.json / User Secrets

The host also reads appsettings.json, user secrets (local dev), and environment variables. Keys under WorkItems:

{
  "WorkItems": {
    "DefaultOrganization": null,
    "DefaultProject": null,
    "TenantId": null,
    "Account": null,
    "MaxResults": 200
  },
  "Debug": false
}

Set "Debug": true (or the BTA_ADO_ARGS_* equivalent) for verbose logging.


Architecture

  • CLI (ByteAid.Tools.Ado.Cli) — System.CommandLine command tree on the generic host (Host.CreateDefaultBuilder + UseHost). Verb groups (show/create/update/delete) are populated per area; the work items area is wired by AddWorkItemsCommands. Input augmentation runs in Program.cs before InvokeAsync.
  • Core (ByteAid.Tools.Ado) — AzureDevOpsTokenProvider (auth + cache, registered as a singleton via an async factory) and per-area services that depend only on IAzureDevOpsTokenProvider. Work items split read (WorkItemsService) and write (WorkItemWriteService) paths; mutations are sent as application/json-patch+json.
  • Adding an area — add a service in the core library taking IAzureDevOpsTokenProvider, register it in ServiceCollectionExtensions, and attach its commands to the existing verb groups. Auth, input resolution, and logging are inherited unchanged.

Versioning & publishing

Version is single-sourced in Directory.Build.props (<Version>); both projects inherit it. The ci-cd.yaml pipeline packs and publishes to NuGet.org on every push to master (--skip-duplicate, idempotent). To release: bump <Version>, commit, push.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-alpha.2 85 6/16/2026
1.0.0-alpha.1 70 6/15/2026