Skip to content

Repository files navigation

Table of Contents




Choose which Prerequisite CLI's and Other Tools to Install




System Setup for bash and PowerShell Profiles


IMPORTANT FOR MAC USERS There are several use cases of the command "start" that allows files and websites to be opened from the terminal. An "if check conditional" has been introduced to create an alias for the mac command "open" to run whenever "start" is entered. From my initial setup on a mac this has been working for me but if any erros around "start is not a command" we can also replace all instances of "start" with "open" locally in your bashcuts clone to your machine.


SETUP FOR BASH TERMINAL:

Shortcuts using bashrc or bash_profile files. Many of the shortcuts provide prompts to support populated necessary arguments/flags to make the functions work

You may not already have a .bashrc file on your system. To create one, open a bash terminal and copy and paste the below command in the terminal or create a new file in your user directory with the name .bashrc

touch ~/.bashrc

You can also use the .bash_profile file instead of the .bashrc.

To get started, add the below content and associated logic to the your .bashrc file and from there it will load up all the aliases and functions referenced from the .bcut_home file.

To open the .bashrc file that was created above type in the terminal: start ~/.bashrc

IMPORTANT -- clone the bashcuts directory into a folder directory structure without spaces or the source command won't be able to evaluate the path correctly (still working on setting it up correctly to not care about spaces). Also note you will have to provide that path to the variable below:

PATH_TO_BASHCUTS="/c/path/to/your-parent-directory-where-bashcuts-will-be-cloned-into/"  
if [ -f $PATH_TO_BASHCUTS/bashcuts/.bcut_home ]; 
then 
    echo "bashrc loaded"
    source $PATH_TO_BASHCUTS/bashcuts/.bcut_home
else
    echo "missing bashrc"
fi
	

SETUP FOR PowerShell Terminal AND PowerShell Debugger Terminal in VS Code:

Once PowerShell Core has been installed on your machine you can open up a new PowerShell terminal in VS Code or a standalone PowerShell Terminal.

New to PowerShell profiles or the Azure CLI? See Machine Setup: PowerShell Profile & Azure CLI below for a from-zero walkthrough of finding, creating, and editing your $profile (with screenshots) plus installing and configuring az. The snippet below assumes you already have a profile file open for editing.

With the PowerShell Profile open add the following code snippet AND IMPORTANT replace the path directories to point to where the bashcuts directory was cloned to.

We will know if its working as expected if the terminal prompts out "powershell starting" on initialization/opening:


$path_to_bashcuts_parent_directory = 'C:\git'
$bashcuts_git_directory = "bashcuts"
if ("$path_to_bashcuts_parent_directory\$bashcuts_git_directory" -ne $NULL) {
    $path_to_bashcuts = "$path_to_bashcuts_parent_directory\$bashcuts_git_directory"
    Write-Host "PowerShell bashcuts exists"
	. "$path_to_bashcuts\powcuts_home.ps1"
} else {
	Write-Host "Cannot find bashcuts"
    Write-Host "pow_home not setup"
}

For the PowerShell terminal from the VS Code PowerShell extension, we can use the same steps as above. It more than likely will be a different profile to update. See Machine Setup: PowerShell Profile & Azure CLI for the difference between the standalone-pwsh profile and the VS Code-host profile.



Machine Setup: PowerShell Profile & Azure CLI

This section is a from-zero walkthrough for a fresh machine: get your PowerShell $profile under control, then install and configure the Azure CLI (az) so the az-* shortcuts work. Everything below is one-time-per-machine setup.


Managing your PowerShell profile

Your $profile is the script PowerShell runs every time a terminal starts — it's where bashcuts gets wired in (see System Setup above) and where you set the $env:AZ_* variables the Azure DevOps shortcuts read.

Find the active profile

$profile                      # path PowerShell loads for the current host
$PROFILE.CurrentUserAllHosts  # shared across every PowerShell host on this machine

Heads up — VS Code uses a different profile. A standalone pwsh terminal and the VS Code PowerShell-extension terminal each load their own $profile path. Run $profile in each terminal to see the two distinct paths; if a shortcut works in one but not the other, you've likely only wired up one of them. $PROFILE.CurrentUserAllHosts is the path that applies to both, if you'd rather maintain a single file.

Create it if it doesn't exist

New-Item -ItemType File -Path $profile -Force

-Force creates any missing parent directories so this works on a clean machine.

Edit it

code $profile    # open in VS Code (recommended — syntax highlighting for .ps1)
start $profile   # or open in your OS default editor

If start $profile prompts for which application to open the file in, choose VS Code and tick the checkbox to use VS Code for all .ps1 files.

Here's a screen shot of the empty profile being opened in VS Code:

image

Here's a side-by-side view of a regular PowerShell core terminal and the PowerShell VS Code extension terminal — note each has its own profile:

image

Reload after editing

You don't have to close and reopen the terminal — dot-source the profile to re-run it in the current session:

. $profile

Configuring the Azure CLI (az)

The Azure DevOps shortcuts (az-Connect-AzDevOps, az-Sync-AzDevOpsCache, the work-item views and creators) all shell out to the Azure CLI. Get az installed and configured once and they all light up. (az-Connect-AzDevOps automates several of these steps interactively on first run — this is the manual reference.)

1. Install the Azure CLI

Follow Microsoft's per-OS installer: https://aka.ms/installazurecli

2. Verify the install

az version

You should see a JSON blob with azure-cli and your installed version. If az isn't found, reopen your terminal so the updated PATH takes effect.

3. Sign in

az login

This opens a browser to authenticate. On a headless box, or when the browser flow won't complete, use device-code auth:

az login --use-device-code

If your organization spans multiple Entra ID tenants, target the right one explicitly:

az login --tenant <tenant-id-or-domain>

4. Keep it current

az upgrade

The azure-devops extension and the az boards surface move quickly; an out-of-date CLI is a common cause of confusing errors.

5. Pick the right subscription

az account show                          # which subscription am I on?
az account list --output table           # all subscriptions available to me
az account set --subscription "<name-or-id>"

6. Add the azure-devops extension

Required for every az boards / az devops call the shortcuts make:

az extension add --name azure-devops

(az-Connect-AzDevOps will offer to install this for you on first run.)

7. Set your org and project defaults

Org and project are stored in your Azure CLI profile — not in PowerShell env vars — so you only do this once per machine (or after switching orgs):

az devops configure --defaults organization='https://dev.azure.com/myorg' project='My Project'

With those seven steps done, head to Azure DevOps work-item shortcuts to set the optional $env:AZ_* profile variables and run az-Connect-AzDevOps to confirm everything is wired up.



How to use bashcuts


The bashcuts commands (for the majority of commands) have a convention of 'verb-noun' and meant to be auto-filled with tab-tab to avoid any typos or copy/paste mistakes

image

Press tab twice for auto-fill and available command options:

image

To See Where Shortcuts are Loaded and may be available

  • "o-" for "Open" --> "o-sfdx" will open the file containing all aliases and supporting logic for sfdx cli shortcuts. With the sfdx-bashcuts (or any bashcuts commands file) can be easily searched, modified, or new commands added and can be committed. When modifying files enter the command "reinit" when done to reload the current terminal instead of closing and repopening.
  • To see all possible aliases and associated functions, in bash terminal, type "o-" and then press tab twice to see options of each file of shortcuts image

image

Outlook day agenda (ol-, PowerShell + Windows)

If you run desktop Outlook on Windows, the ol- shortcuts (in powcuts_by_cli/outlook_agenda.ps1) show your day from the terminal — no alt-tab into Outlook:

  • ol-Show-OutlookDay (alias ol-day) — today's meetings, then the open tasks you're expected to work on (due today or overdue)
  • ol-Get-OutlookAgenda / ol-Get-OutlookTasks — the same data as objects you can pipe/filter (ol-Get-OutlookAgenda | Format-List)
  • ol-Show-OutlookAgenda / ol-Show-OutlookTasks — the individual tables

These read the Calendar and Tasks folders via Outlook COM automation, so they require Windows + the desktop Outlook client (no cloud/Graph auth). On macOS/Linux — or if Outlook can't be reached — every command prints a short hint and exits cleanly.

If you also use the Azure DevOps (az-*) shortcuts, your assigned work items appear automatically as a 🎯 Azure DevOps section at the bottom of ol-Show-OutlookDay — the active items in your current iteration, sorted by priority. It's read-only and served straight from the existing assigned cache (no extra sync or az callout), so run az-Sync-AzDevOpsCache if the section says the cache isn't found. Pass ol-Show-OutlookDay -NoWorkItems to hide it and show only the built-in agenda + tasks. The Outlook module has no hard dependency on az-*: on a machine without the Azure DevOps shortcuts the section simply doesn't appear.

Opening VS Code Snipppets

When using VS Code it is HIGHLY recommend to setup VSCode settings sync: https://code.visualstudio.com/docs/editor/settings-sync

This will sync settings, keyboard shortcuts, much more, AND SNIPPETS.

For windows machines, the snippets are stored in an expected directory, so we can created custom snippet files or use VSCode made snippet files and quickly open them to add new snippets as needed. VS-Code saves those snippets via sync and bashcuts allows us to easily open, edit, and add to them without leaving our VS Code editor.

image



Azure DevOps work-item shortcuts

Lost in the surface? Run az-help (defined in powcuts_by_cli/azdevops_help.ps1) for an interactive Out-ConsoleGridView walkthrough that groups every az-AzDevOps* function by workflow phase (Onboarding → DailyRead → Create → MultiProject) and shows the order of operations + source / diagram / issue links for each function. az-h<Tab> lands directly on it.

PowerShell shortcuts in powcuts_by_cli/azdevops_*.ps1 (split across azdevops_auth.ps1, azdevops_paths.ps1, azdevops_sync.ps1, azdevops_views.ps1, azdevops_workitems.ps1, azdevops_find.ps1, azdevops_classification.ps1, azdevops_create_pickers.ps1, azdevops_create.ps1, azdevops_draft.ps1, azdevops_schema.ps1, azdevops_openers.ps1) provide guided setup and work-item navigation against an Azure DevOps organization. Today this includes a guided az-Connect-AzDevOps first-run helper, a cached background sync (az-Sync-AzDevOpsCache, which also refreshes itself silently on shell open when the cache is stale), a pickable/display-only grid pair plus a browser opener for items assigned to you (az-Get-AzDevOpsAssigned, az-Show-Assigned, and az-Open-Assigned which opens the "Assigned to me" web view), the matching pair for items where you've been @-mentioned in discussion (az-Get-AzDevOpsMentions, az-Open-Mention), a merged recent-activity view of non-closed items you've posted on or been tagged in, newest first (az-Show-RecentActivity), an open-any-item-by-id shortcut (az-Open-WorkItemById), an Epic→Feature→User Story tree view (az-Show-Tree, rendering the project's requirement-tier rows — User Story on Agile, Product Backlog Item on Scrum, Requirement on CMMI, Issue on Basic), a flat fuzzy search across every cached work item regardless of tier (az-Find-AzDevOpsItem, which ranks the hits and hands them to the same open-in-browser / create-child grid dispatcher), a board-style group-by-State view of the same cached items (az-Show-Board), an orphan finder that lists parentless Features and stories under the active area so stray items can be re-parented (az-Show-Orphans), area- and iteration-path tree views (az-Show-Areas, az-Show-Iterations), an interactive Epic→Feature→Story drill-down picker (az-Find-AzDevOpsWorkItem), an interactive new-user-story creator with parent-feature, iteration, and area-path pickers (az-New-AzDevOpsUserStory), an interactive new-Feature creator one tier up (parent-Epic picker, area / iteration / priority / AC) with a hand-off prompt to spawn child stories (az-New-AzDevOpsFeature), a top-tier Epic creator with no parent picker (az-New-AzDevOpsEpic) — which the Story and Feature creators can also spawn inline from their orphan path (pick "no parent" and they offer to create the missing Feature/Epic and link to it in one flow), a batch child-story creator that decomposes a Feature into 3-7 stories with a single area / iteration captured once and priority / story points carried forward across the loop (az-New-AzDevOpsFeatureStories), an interactive Task creator one tier down with a parent-User-Story picker (az-New-Task, also the child action when you select a Story in az-Show-Tree / az-Show-Board), and a per-org field-schema config (az-Initialize-AzDevOpsSchema, az-Get-AzDevOpsSchema, az-Edit-AzDevOpsSchema, az-Test-AzDevOpsSchema) that future schema-aware updates to the work-item commands consume.

Prerequisites

The Azure CLI (az), the azure-devops extension, an active az login session, and your org/project defaults (az devops configure --defaults) all need to be in place. See Machine Setup: PowerShell Profile & Azure CLI for the full from-zero walkthrough — or just run az-Connect-AzDevOps, which checks each prerequisite and offers to install the extension / start an az login for you on first run.

Optional profile environment variables

Add any of these to your PowerShell $profile to enable additional features:

$env:AZ_USER_EMAIL = 'user@example.com'   # enables accurate mentions WIQL
$env:AZ_AREA       = 'My Project\My Team' # default area path for hierarchy queries
$env:AZ_ITERATION  = 'My Project\Sprint 42' # default iteration for work item creation
$env:AZ_TEAM       = 'alice@example.com;bob@example.com' # extra teammates taggable in debriefs (supplements your project team)
$env:BASHCUTS_NO_SPINNER = '1'            # opt out of the az-call loading spinner

Every AzDevOps command routes its underlying az call through one wrapper, which now shows a small terminal spinner while the call is in flight and clears it cleanly when the call returns — so a multi-second az-Sync-AzDevOpsCache or az-New-AzDevOpsUserStory always signals that work is happening. The spinner suppresses itself when output is redirected or piped (CI, > out.txt, | cmd) so it never leaks into captured JSON; set BASHCUTS_NO_SPINNER to turn it off everywhere.

First run

In a fresh PowerShell terminal:

az-Connect-AzDevOps

This walks through seven checks (Azure CLI present, azure-devops extension installed, env vars set, az login session active, az devops defaults configured, user-machine WIQL query files seeded, smoke az boards query succeeds) and prints a clear READY or NOT READY verdict at the end. It will offer to install the extension and run az login for you if either is missing.

After az-Connect-AzDevOps reports READY once, the read/sync commands (az-Sync-AzDevOpsCache, the schema commands) use the silent az-Test-AzDevOpsAuth check at startup to confirm the environment is still good before they hit the cache. The result is cached in-session for a few minutes so the check isn't repeated on every command. The az-New-AzDevOps* creators skip that live check entirely — they only confirm the az CLI is on PATH and let the az boards work-item create call itself surface any auth problem, so creating a work item makes no extra az round-trip beyond the create and parent-link.

Customizing WIQL queries

The hierarchy dataset that az-Sync-AzDevOpsCache writes into hierarchy.json is built from three per-tier WIQL files on your own machine, not from code in this repo:

  • POSIX: ~/.bashcuts-az-devops-app/config/queries/{epics,features,user-stories}.wiql
  • Windows: %USERPROFILE%\.bashcuts-az-devops-app\config\queries\{epics,features,user-stories}.wiql

Each sync fires one az boards query per file (epics, then features, then user stories) and merges the results into a single hierarchy.json. Splitting per tier sidesteps the partial-result behavior that the previous single combined query hit on larger projects, and lets you tune each tier's filter independently.

az-Connect-AzDevOps (and the first run of az-Sync-AzDevOpsCache if you skipped Connect) seeds each file with a sensible default — items under {{AZ_AREA}} scoped to one of Microsoft.EpicCategory / Microsoft.FeatureCategory / Microsoft.RequirementCategory, where {{AZ_AREA}} is substituted from $env:AZ_AREA at read time. Edit any file to add fields to the SELECT clause, filter by state, scope by tag, or otherwise tailor what lands in hierarchy.json. Re-run az-Sync-AzDevOpsCache to pick up your changes — no reinit needed, since the files are read every sync.

The fast way to open all three for editing is the dedicated shortcut:

az-Open-HierarchyWiqls

It seeds any missing defaults (so it works on a fresh machine even before az-Connect-AzDevOps) and then opens each path in your OS default editor.

If you delete a file, the next sync writes that default back. The placeholder {{AZ_AREA}} is the only one currently supported; everything else in each file is passed through to az boards query --wiql verbatim.

Opening cache / config / schema files

Every folder and file under ~/.bashcuts-az-devops-app/ has a dedicated opener — most under the az-Open-* prefix (tab-tab on az-Open- to see the full list), with the config-queries and schema directories under the o-az-devops-* open prefix. Each opener launches the path in your OS default handler (Start-Process); if the target doesn't exist yet it prints a one-line yellow hint pointing at the function that produces it (e.g. az-Sync-AzDevOpsCache) and returns without spawning anything.

Folders:

az-Open-AppRoot                 # ~/.bashcuts-az-devops-app/
az-Open-CacheDir                # cache/  (or cache/<project-slug>/ when az-Use-AzDevOpsProject is active)
o-az-devops-queries-config-dir  # config/queries/
o-az-devops-schema-dir          # schema/

Cache files (all resolved through the active project slice):

az-Open-AssignedCache    # assigned.json
az-Open-MentionsCache    # mentions.json
az-Open-HierarchyCache   # hierarchy.json
az-Open-IterationsCache  # iterations.json
az-Open-AreasCache       # areas.json
az-Open-LastSync         # last-sync.json
az-Open-SyncLog          # sync.log

Config WIQL files (per file; az-Open-HierarchyWiqls above remains the "open all three" convenience):

az-Open-EpicsWiql        # config/queries/epics.wiql
az-Open-FeaturesWiql     # config/queries/features.wiql
az-Open-UserStoriesWiql  # config/queries/user-stories.wiql

Field-templates config (extra az boards work-item create fields per type — see "Declaring extra create fields" below):

az-Open-FieldTemplates   # config/field-templates.json  (+ the seeded .example.json)

Body-templates config (custom User Story Description body with prompt placeholders — see "Custom User Story body templates" below):

az-Open-BodyTemplates    # config/body-templates.json   (+ the seeded .example.json)

Schema file (per-org schema-<slug>.json, falling back to schema.json when $env:AZ_DEVOPS_ORG is unset):

az-Open-Schema

Day-to-day work-item shortcuts

These read the local cache populated by az-Sync-AzDevOpsCache (which also runs silently in the background on shell open when the cache is stale — see below). They never call az directly, so they return instantly.

az-Get-AzDevOpsAssigned                       # pickable grid of everything assigned to you (excludes Closed/Removed); tick rows to pipe onward
az-Get-AzDevOpsAssigned -State Active         # filter to a single state
az-Get-AzDevOpsAssigned -State Active,New     # filter to multiple states
az-Get-AzDevOpsAssigned | Format-Table -AutoSize

az-Show-Assigned                              # same grid, display-only (no row selection)
az-Show-Assigned -State Active                # display-only grid filtered to a state

az-Open-Assigned                              # open the "Assigned to me" web view in your browser (all items assigned to you)
az-Open-WorkItemById 12345                    # open ANY work item by id in the browser (no cache lookup; works even if it isn't assigned to / mentioning you)

az-Get-AzDevOpsMentions                                  # work items where you've been @-mentioned (excludes items you're already assigned to)
az-Get-AzDevOpsMentions -State Active                    # filter to a single state
az-Get-AzDevOpsMentions -Since (Get-Date).AddDays(-7)    # only mentions whose last activity was in the past week
az-Get-AzDevOpsMentions -IncludeAssigned                 # also surface mentioned items already assigned to you
az-Get-AzDevOpsMentions | Format-Table -AutoSize

az-Open-Mention 12345                         # open one of your mentioned items in the browser

az-Show-RecentActivity                        # merged grid of non-closed items you posted on OR were tagged in, newest first; Reason column flags Posted / Tagged / Both
az-Show-RecentActivity -State Active,New      # filter to one or more states (default excludes Closed/Removed)

az-Show-AzDevOpsDigest                        # compact activity digest (new comments, new items this week, open commented stories) from the cache; also prints automatically on shell open

az-Show-Tree                          # Epic -> Feature -> User Story tree; select a row to open it or create a child work item
az-Show-Board                         # board view: cached items grouped by State (click the State header in the grid to group)
az-Show-Board -State Active,New       # filter to one or more states (default excludes Closed/Removed)
az-Show-Board -State Closed,Resolved  # flip to the archive view
az-Show-Board -Type Bug,Task          # custom-template work-item types (default: Epic, Feature, User Story)

az-Show-Epics                         # Epics from the active project's hierarchy cache; select a row to open it or create a child Feature
az-Show-Epics -State Closed,Resolved  # flip to the archive view
                                      # tick several rows -> prompted to open them all in the browser at once

az-Show-Features                      # cross-project Features view: every project in $global:AzDevOpsProjectMap, tagged with a Project column
az-Show-Features -Project ProjectABC  # narrow to one registered project's Features (no need to az-Use-AzDevOpsProject first)
az-Show-Features -State Closed        # flip to the archive view across all projects

az-Show-Orphans                       # parentless Features + stories under the active area (Epics excluded - they're roots); select a row to open it or create a child
az-Show-Orphans -Area 'ProjABC\Team'  # narrow to a sub-path of the cached area
az-Show-Orphans -State Closed,Resolved # include closed orphans (default shows active only)

az-Show-CurrentSprint                 # items in the active sprint (resolved from iteration dates - today within start/finish); closed items sorted to the bottom; select a row to open it or create a child
az-Show-ItemsBySprint                 # pick a sprint (same iteration picker as the create flow), then list its items; closed items sorted to the bottom
az-Show-ItemsBySprint -Iteration 'My Project\Sprint 42'  # skip the picker and target a sprint directly

az-Show-Areas                         # print the project's area-path tree (cache-first, live fallback)
az-Show-Iterations                    # print the project's iteration-path tree (with start/finish dates)

az-Get-AzDevOpsAreas                          # pipeable rows for the area tree (Depth / Name / Path / HasChildren)
az-Get-AzDevOpsIterations                     # pipeable iteration rows + [datetime]? StartDate / FinishDate, e.g.:
az-Get-AzDevOpsIterations | Where-Object { $_.StartDate -le (Get-Date) -and $_.FinishDate -ge (Get-Date) }  # the current sprint

az-Find-AzDevOpsWorkItem                      # interactive drill-down: pick an Epic, then a Feature, then a Story; '.. [Go Back]' climbs a tier; '.. [Open this <Type>]' launches the browser; loops until you pick 'EXIT' or hit Esc
az-Find-AzDevOpsWorkItem -IncludeClosed       # include Closed/Removed items in the drill-down grids
az-Find-AzDevOpsText "deploy pipeline"        # fuzzy filter across the whole hierarchy cache over Title + Description (every whitespace token must match); select a row to open it or create a child
az-Find-AzDevOpsText                          # no query: open the full hierarchy in the grid and filter live (the grid's own filter box searches Title + Description)
az-Find-AzDevOpsText -IncludeClosed auth      # include Closed/Removed items in the search
az-Find-AzDevOpsItem login                    # flat fuzzy search across ALL cached items (no Epic-first drill-down); ranks hits, then open in browser or create a child from the grid
az-Find-AzDevOpsItem                          # no query arg -> prompts for one (blank lists everything); use the grid's own filter box to narrow
az-Find-AzDevOpsItem api -Type Feature -IncludeClosed  # scope the fuzzy match to a work-item type and include Closed/Removed
az-Find-AzDevOpsArea                          # pick an area path from the tree; emits the picked Path on the pipeline
az-Find-AzDevOpsIteration                     # pick an iteration; grid shows StartDate / FinishDate columns; emits Path
az-Find-AzDevOpsProject                       # pick from $global:AzDevOpsProjectMap (Active / Name / Project / Org)
az-Find-AzDevOpsProject -Use                  # ... and switch to it (calls az-Use-AzDevOpsProject on the pick)

az-Find-AzDevOpsText is the quick text search over everything in the hierarchy cache: pass a query and every whitespace-separated token must appear (case-insensitively) somewhere in a work item's Title or Description for it to show. Pass no query to dump the whole hierarchy into the grid and lean on Out-ConsoleGridView's own live-filter box. Selecting a row opens it in the browser or creates its hierarchical child, exactly like the az-Show-* views. Description search relies on [System.Description] being in your hierarchy WIQLs — it ships in the seeded defaults, but if you seeded your epics.wiql / features.wiql / user-stories.wiql before this field was added, run az-Open-HierarchyWiqls, add [System.Description] to each SELECT, and re-run az-Sync-AzDevOpsCache (the command prints a one-line tip when it notices no descriptions are cached). Title-only search works regardless.

If the cache is older than 6 hours, az-Get-AzDevOpsAssigned, az-Show-Assigned, az-Get-AzDevOpsMentions, az-Show-Tree, az-Show-Board, az-Show-Epics, az-Show-Features, az-Show-Orphans, az-Show-CurrentSprint, az-Show-ItemsBySprint, az-Show-Areas, az-Show-Iterations, az-Find-AzDevOpsWorkItem, az-Find-AzDevOpsItem, and az-Find-AzDevOpsText each print a one-line WARNING stale (last sync: ...) notice above their output and still render the cached data.

Automatic on-open refresh. You don't have to schedule anything. Every time a PowerShell session loads bashcuts, it checks the cache age and — if it's stale (older than 6 hours, or never synced) — spawns a detached, hidden pwsh that runs az-Sync-AzDevOpsCache in the background. Your prompt returns instantly and no sync chatter prints into the terminal; the next command you run reads the freshly-updated cache (the background sync's progress goes to ~/.bashcuts-az-devops-app/cache/sync.log). The background process does the network auth check itself and exits quietly if you're not connected, so a not-connected shell is a silent no-op. To disable this entirely, set $env:AZ_DEVOPS_NO_AUTOSYNC = '1' in your $profile (run az-Sync-AzDevOpsCache by hand when you want a refresh). A short-lived lock under the cache directory keeps several terminals opened in quick succession from each kicking off a sync.

Startup activity digest. Every PowerShell session also prints a compact az-Show-AzDevOpsDigest on open — a non-blocking, cache-only glance at what's moved in Azure DevOps since the last sync, with no keypress and no az round-trip. It reads the same cached hierarchy.json and shows up to four sections, each omitted entirely when it has no rows:

  • New comments since yesterday — items changed since yesterday 00:00 that carry discussion comments
  • New items this week — items created since Monday 00:00 of the current week
  • New comments this week — items changed since Monday 00:00 that carry comments
  • Open stories with comments — non-closed requirement-tier items (User Story / PBI / Bug) that have comments

When nothing matches — or the cache doesn't exist yet — the digest prints nothing at all, so a fresh or not-configured shell stays quiet. Run az-Show-AzDevOpsDigest any time to see it on demand, and set $env:AZ_DEVOPS_NO_DIGEST = '1' in your $profile to suppress the automatic on-open print (the manual command still works). The comment counts and change/create dates come from fields Azure DevOps returns on each cached work item, so no WIQL or sync changes are needed.

az-Show-Tree (and any future hierarchy-view commands) include a Url column on every row so you can copy or click straight to the work item from Out-ConsoleGridView.

After you select a row in az-Show-Tree, az-Show-Board, az-Show-Epics, az-Show-Features, az-Show-Orphans, az-Show-CurrentSprint, or az-Show-ItemsBySprint, you're prompted to either open the item in your browser or create the hierarchically-appropriate child work item — a Feature under an Epic, a User Story under a Feature, or a Task under a User Story (via az-New-Task) — with the selected row pre-filled as the new item's parent. When you tick more than one row, you're first offered a single "Open all N in browser?" shortcut (Enter opens them all); decline it to fall through to the per-row open/create-child prompt. Selecting a node in az-Show-Areas / az-Show-Iterations offers to open the project's Boards hub. (The post-selection prompt is PowerShell-only; the bash az-show-features shortcut prints a static query table.)

Every list and picker (az-Get-AzDevOpsAssigned, az-Show-Assigned, az-Get-AzDevOpsMentions, az-Get-AzDevOpsSchema, az-Get-AzDevOpsCacheStatus, az-Show-Tree, az-Show-Board, az-Show-Epics, az-Show-Features, az-Show-Orphans, az-Show-Areas, az-Show-Iterations, az-Find-AzDevOpsWorkItem, az-Find-AzDevOpsText, plus the parent-Feature / iteration / area pickers in az-New-AzDevOpsUserStory) renders through Out-ConsoleGridView — a sortable, filterable, click-to-select TUI grid that runs in your terminal on Windows, macOS, and Linux. Use the arrow keys to navigate, Space to select rows, Enter to confirm, Esc to cancel. Selected rows from the listing functions are emitted to the pipeline, so e.g. az-Get-AzDevOpsAssigned | ForEach-Object { az-Open-WorkItemById $_.Id } opens every row you ticked. The grid ships in a separate module — install once with Install-Module Microsoft.PowerShell.ConsoleGuiTools -Scope CurrentUser. If the module isn't installed, every command falls back to the existing Format-Table / numbered-menu output — except az-Find-AzDevOpsWorkItem, which is grid-only by design and prints the install hint above instead of running.

az-Sync-AzDevOpsCache populates two more cache files alongside the existing assigned.json / mentions.json / hierarchy.json: iterations.json and areas.json. The new-user-story command below uses these for instant iteration / area-path pickers; if you've upgraded but haven't re-synced yet, the picker fetches them live with a one-line "(run az-Sync-AzDevOpsCache to make this instant)" notice.

Verb coverage at a glance

Tab-tab on az-Get-, az-Show-, or az-Find- to discover commands. The matrix is also kept inline at the top of powcuts_by_cli/azdevops_auth.ps1.

Noun az-Get- az-Show- az-Find-
Project Projects Project Project
Work Item (hierarchy) Tree, Board, Epics WorkItem, Text
Work Item (assigned) Assigned (pickable)
Work Item (mentions) Mentions (pickable)
Area Areas Areas Area
Iteration Iterations Iterations Iteration
Cache CacheStatus
Schema Schema

az-Get-* returns pipeable rows ([PSCustomObject]); az-Show-* writes a human-readable view; az-Find-* is an interactive picker over Out-ConsoleGridView that emits the picked value on the pipeline. Assigned / Mentions already open a grid via Show-AzDevOpsRows -PassThru so they double as pickers; that's why there is no separate Show- or Find- for them.

Creating a new User Story

az-New-AzDevOpsUserStory walks you through title / description / priority / story points / acceptance criteria, then offers an interactive picker for the parent Feature (active Features pulled from hierarchy.json), the iteration, and the area path. The picker uses Out-ConsoleGridView when available, otherwise a Read-Host numbered menu (see "Day-to-day work-item shortcuts" above for the fallback rules). After it creates the story it links the chosen parent and opens the new work item in your browser.

az-New-AzDevOpsUserStory                       # full interactive walk-through

Every prompt is skippable via a parameter, so the function works non-interactively in a script:

az-New-AzDevOpsUserStory `
    -Title              "Add new dashboard widget" `
    -Description        "Surface deploy frequency on the team home page." `
    -Priority           2 `
    -StoryPoints        3 `
    -AcceptanceCriteria "- Widget renders for all team members`n- Updates within 60s of new deploy" `
    -FeatureId          1240 `
    -Iteration          "My Project\Sprint 42" `
    -Area               "My Project\My Team" `
    -NoOpen

-FeatureId 0 creates an orphan (no parent link) with no prompt. -NoOpen skips the browser launch and just echoes the new work-item URL — handy in scripts.

When you reach the parent-Feature picker interactively and pick 0 / cancel (orphan), the creator asks Create a new parent Feature now? [y/N] first. On yes it runs the full az-New-AzDevOpsFeature walk-through (which can itself chain up to a new Epic), then links your new Story to the Feature it just created — so you can build a fresh Epic → Feature → Story slice in one command. On no/Enter it creates the orphan exactly as before. (Passing -FeatureId 0 explicitly still means "force orphan" and skips this prompt.)

Declaring extra create fields (field-templates.json)

Every az-New-AzDevOps* creator can prompt for extra work-item fields beyond the built-in title / description / priority / etc. — things like a swimlane, a business-value note, or a team tag. Declare them per work-item type in ~/.bashcuts-az-devops-app/config/field-templates.json, keyed by Azure DevOps field reference name (e.g. Custom.Swimlane, not the display name). Each field's value is one of three shapes:

Value Behavior at create time
{ "mode": "grid", "options": ["Expedite","Standard","Blocked"] } Single-select grid pick over the static options via Out-ConsoleGridView — no typos, sortable / filterable
{ "mode": "prompt" } (or the bare string "prompt") Free-text Read-Host
"SomeLiteral" Passed straight through to --fields unchanged

Grid mode never blocks a create: if Out-ConsoleGridView isn't installed, the options list is empty, or you cancel/escape the grid, it falls back to a free-text Read-Host (an empty answer just skips the field). The config keys are USER_STORY, FEATURE, TASK, and EPIC:

{
  "USER_STORY": {
    "Custom.Swimlane": { "mode": "grid", "options": ["Expedite", "Standard", "Blocked"] },
    "Custom.BusinessValue": { "mode": "prompt" }
  }
}

az-Connect-AzDevOps (and the first create / az-Open-FieldTemplates) seeds an empty field-templates.json ({}, so nothing extra is prompted until you opt in) plus a field-templates.example.json documenting the swimlane example above. Open both for editing with:

az-Open-FieldTemplates

The same shape can also be authored in your $profile under $global:AzDevOpsProjectMap[...].WORKITEMTYPE_OVERRIDES.<TYPE>.RequiredFields — the two sources are merged, and a RequiredFields entry overrides the field-templates.json entry for the same field. This is the project-scoped escape hatch when one board needs a different option set than the machine-wide JSON.

Fields that don't stick on create. Some fields — board swimlane / lane fields and certain custom fields — are silently ignored by az boards work-item create even though the command echo shows them going out and az exits 0. After each create, the creators read the returned item back and re-apply any requested extra field that didn't land via a follow-up az boards work-item update (which honors these fields on an existing item). If a field is set by neither call, you'll see a yellow warning naming the ref name and value — the usual cause is a wrong reference name (use Custom.Swimlane, not the display name) or a field that isn't on that work-item type's process.

Custom User Story body templates (body-templates.json)

Don't like the built-in As a / I want / So that Description prompts? Supply your own body template — a single string with numbered prompt placeholders — and az-New-AzDevOpsUserStory will ask your questions in your order and drop each answer back exactly where its placeholder sat. A placeholder looks like:

[[ PROMPT_<n>--<prompt text shown to you> ]]
  • <n> is a unique integer that drives ask orderPROMPT_1 is asked before PROMPT_2, no matter where each sits in the template.
  • The text after -- is the exact prompt you see at the Read-Host.
  • Where the placeholder sits in the template is where your answer lands (ask-order and text-position are independent).

Declare one template per work-item type (User Story today) in ~/.bashcuts-az-devops-app/config/body-templates.json, keyed by work-item type. JSON strings are single-line, so use <br/> for line breaks — or a \n escape, since real newlines are automatically converted to <br/> when the body is built (the AzDO Description field is HTML):

{
  "USER_STORY": "<b>Scenario</b><br/>Given [[ PROMPT_2--the starting state ]]<br/>When [[ PROMPT_3--the action taken ]]<br/>Then [[ PROMPT_1--the expected outcome ]]"
}

With that in place the creator asks the expected outcome first (PROMPT_1), then the starting state (PROMPT_2), then the action taken (PROMPT_3), and renders a Given/When/Then body with each answer in position. A template with no placeholders is used verbatim; a malformed one (a stray [[ PROMPT… ]] block or a duplicate number) warns and falls back to the stock prompts, so a create is never blocked.

az-Connect-AzDevOps (and the first create / az-Open-BodyTemplates) seeds an empty body-templates.json ({}, so you keep the stock prompts until you opt in) plus a body-templates.example.json documenting the Given/When/Then example. Open both for editing with:

az-Open-BodyTemplates

For a project-scoped template, author it in your $profile as a here-string — the natural home for multi-line text — under $global:AzDevOpsProjectMap[...].WORKITEMTYPE_OVERRIDES.<TYPE>.BodyTemplate; it overrides the body-templates.json entry for that type. Just write it across multiple lines — the real newlines are auto-converted to <br/>, so each line renders on its own in the work item and you never hand-type <br/>. Use a single-quoted @'…'@ here-string so a literal $ in your template isn't interpolated:

USER_STORY = @{
    BodyTemplate = @'
Given [[ PROMPT_2--the starting state ]]
When [[ PROMPT_3--the action taken ]]
Then [[ PROMPT_1--the expected outcome ]]
'@
}

The custom template also feeds the az-New-AzDevOpsFeatureStories batch loop and the az-New-AzDevOpsDraft brain-dump, since all three share the same Description reader.

Worked example — what you'll see. With the Given/When/Then template above configured, running az-New-AzDevOpsUserStory prompts you in numeric order (not the order the placeholders appear in the template):

the expected outcome: the cart shows the promo discount
the starting state: a shopper has a $50 cart and a valid 10%-off code
the action taken: they apply the code at checkout

…and the created work item's Description field renders with each answer dropped into its placeholder's spot:

<b>Scenario</b>
Given a shopper has a $50 cart and a valid 10%-off code
When they apply the code at checkout
Then the cart shows the promo discount

More template shapes. The placeholder mechanism is structure-agnostic — the numbers only drive ask order, and the surrounding text is whatever you want. A few more you can drop into body-templates.json:

{
  "USER_STORY": "<b>Bug repro</b><br/><b>Steps:</b> [[ PROMPT_1--the exact steps to reproduce ]]<br/><b>Expected:</b> [[ PROMPT_2--what should happen ]]<br/><b>Actual:</b> [[ PROMPT_3--what actually happens ]]<br/><b>Env:</b> [[ PROMPT_4--browser / OS / build ]]"
}
{
  "USER_STORY": "As a [[ PROMPT_1--persona ]] I want [[ PROMPT_2--capability ]] so that [[ PROMPT_3--benefit ]].<br/><br/><b>Notes:</b> [[ PROMPT_4--anything the implementer should know ]]"
}

Or the project-scoped here-string equivalent (multi-line, no \n/<br/> juggling) in your $profile:

$global:AzDevOpsProjectMap = @{
    ProjectABC = @{
        Org       = 'https://dev.azure.com/myorg'
        Project   = 'Project ABC'
        WORKITEMTYPE_OVERRIDES = @{
            USER_STORY = @{
                BodyTemplate = @'
<b>Bug repro</b>
Steps: [[ PROMPT_1--the exact steps to reproduce ]]
Expected: [[ PROMPT_2--what should happen ]]
Actual: [[ PROMPT_3--what actually happens ]]
Env: [[ PROMPT_4--browser / OS / build ]]
'@
            }
        }
    }
}

Numbers don't have to start at 1 or be contiguous — PROMPT_10, PROMPT_20, PROMPT_30 sorts the same as 1, 2, 3 and leaves room to insert a prompt later without renumbering. Each number must be unique within a template; every prompt is required (you'll be re-asked until you enter a non-empty value), matching the stock clause prompts.

Creating a new Feature

az-New-AzDevOpsFeature is the tier-one-up counterpart to the user-story creator. It walks you through title / description / priority, then offers an interactive picker for the parent Epic (active Epics pulled from hierarchy.json), the iteration, and the area path. Same Out-ConsoleGridView / Read-Host fallback rules. The description is built from two guided prompts — Summary and Business Value — each rendered as a bold heading in the work-item Description field. Story points and acceptance criteria are intentionally skipped — Features don't carry story points in the default Agile / Scrum templates, and acceptance criteria belong on the child User Stories.

After it creates the Feature and links the chosen Epic, it asks Add child stories now? [Y/n]; on yes it hands off to az-New-AzDevOpsFeatureStories -ParentId <newFeatureId> with the same area / iteration pre-seeded so you can decompose the Feature into its child stories in the same flow. Pass -NoChildStoriesPrompt to skip the hand-off (useful in scripts).

az-New-AzDevOpsFeature                                  # full interactive walk-through
az-New-AzDevOpsFeature `
    -Title        "Customer impact dashboard" `
    -Description  "Surface customer-impact rollups on the team home page." `
    -Priority     2 `
    -ParentEpicId 1180 `
    -Iteration    "My Project\Sprint 42" `
    -Area         "My Project\My Team" `
    -NoOpen `
    -NoChildStoriesPrompt

-ParentEpicId 0 creates an orphan (no parent link) with no prompt.

Just like the user-story creator, picking 0 / cancel at the interactive parent-Epic picker prompts Create a new parent Epic now? [y/N]. On yes it runs az-New-AzDevOpsEpic and links your new Feature to it; on no/Enter it creates the orphan as before. (Explicit -ParentEpicId 0 skips the prompt.)

Creating a new Epic

az-New-AzDevOpsEpic is the top tier of the Epic → Feature → Story hierarchy, so it has no parent picker — Epics are root items. It mirrors az-New-AzDevOpsFeature's walk-through otherwise (title / description / priority / iteration / area / tags / required fields), then creates the Epic and opens it in your browser. It returns the new Epic's [int] id, which is what makes it usable as the inline "create a parent Epic" target from az-New-AzDevOpsFeature's orphan path. Story points and the child-stories hand-off are intentionally skipped — those belong to the lower tiers.

az-New-AzDevOpsEpic                                # full interactive walk-through
az-New-AzDevOpsEpic `
    -Title       "Customer-impact analytics" `
    -Description "All customer-impact reporting work for FY26." `
    -Priority    2 `
    -Iteration   "My Project\Sprint 42" `
    -Area        "My Project\My Team" `
    -NoOpen

Batch-creating child stories under a Feature

az-New-AzDevOpsFeatureStories -ParentId <feature-id> decomposes an existing Feature into its 3-7 child User Stories without making you re-answer parent / area / iteration on every story. The flow:

  1. Validates the -ParentId resolves to a Feature in hierarchy.json (run az-Sync-AzDevOpsCache first if it doesn't).
  2. Picks the iteration + area once for the whole batch (or accepts -Iteration / -Area to skip the pickers).
  3. Loops: title (Enter to finish the batch) → acceptance criteriapriority (Enter reuses the previous story's answer; any digit overrides) → story points (same [Enter] to reuse shorthand) → creates the story via the same Invoke-AzDevOpsWorkItemCreate + Invoke-AzDevOpsParentLink path the single-shot creator uses, so failure modes / schema enforcement stay identical.
  4. After each story prompts Add another story? (y/N/c). n (default) ends the batch; y continues; c re-picks area / iteration before the next story (escape hatch when one story in the batch belongs to a different sprint).
  5. Mid-batch failures don't abort — the error is logged, the loop continues, and the failed titles are listed at the end so you can retry them via az-New-AzDevOpsUserStory -ParentId $ParentId.
  6. Emits a single summary line — Created N child stories under Feature #1240: 5001, 5002, 5003 (or Created N, Failed M when partial) — followed by one URL per created story for quick post-batch review. Returns [int[]] of the created story ids so it composes into pipelines.
az-New-AzDevOpsFeatureStories -ParentId 1240               # full interactive batch
az-New-AzDevOpsFeatureStories -ParentId 1240 `
    -Iteration "My Project\Sprint 42" `
    -Area      "My Project\My Team"                       # skip the pickers

Draft mode: brain-dump a whole hierarchy, publish once

The az-New-AzDevOps{Epic,Feature,UserStory,Task} creators each make a live az call the moment you finish a single item. Draft mode removes that friction for the case where you want to lay out an entire Epic → Feature → User Story → Task tree in one sitting: every add / edit / re-parent is a local edit to <cache>/draft.json with zero az calls, and nothing hits Azure until you explicitly publish. Parent/child relationships are tracked by a local reference id (#Ref) on each item, so a child can point at a parent that doesn't have a real Azure id yet — the links are resolved and wired at publish time.

Nothing in the draft phase requires auth or the az CLI; only az-Publish-AzDevOpsDraft does.

Guided brain dumpaz-New-AzDevOpsDraft loops: pick a type, give a title, optionally capture details now (or leave them blank and fill in later), pick a parent (from items already in the draft or existing items in hierarchy.json, or orphan). After each item it re-prints the tree with a per-item completeness % so you can see at a glance what still needs detail. A blank type finishes the loop and offers to publish.

az-New-AzDevOpsDraft          # guided loop, then optional publish

Quick / scripted building blocks — the same draft is also editable one command at a time (handy for scripting or filling gaps):

az-Add-AzDevOpsDraftItem -Type Epic    -Title "Q3 Checkout revamp" -Orphan            # returns local Ref, e.g. 1
az-Add-AzDevOpsDraftItem -Type Feature -Title "Guest checkout"     -ParentRef 1        # nest under draft #1
az-Add-AzDevOpsDraftItem -Type "User Story" -Title "Remember cart" -ParentId 1240      # nest under an EXISTING Azure item
az-Set-AzDevOpsDraftItem -Ref 2 -Details                                               # fill in a drafted item's fields later
az-Set-AzDevOpsDraftItem -Ref 3 -Priority 2 -ParentRef 2                               # or set individual fields / re-parent
az-Remove-AzDevOpsDraftItem -Ref 3                                                     # drop it (children reparent to grandparent)
az-Show-AzDevOpsDraft                                                                  # tree + completeness %, no az call

Publishaz-Publish-AzDevOpsDraft resolves the iteration/area once (picker or -Iteration/-Area), then walks the tree parents-first, creating each item via the same Invoke-AzDevOpsWorkItemCreate + Invoke-AzDevOpsParentLink path the single-shot creators use, and adds the parent link in the same pass. A live progress bar reports how far the publish has landed:

[########------------]  40% (4/10) User Story: Remember cart across sessions

A create that fails is reported and its descendants are skipped (their parent never got an id); everything else still publishes. On a clean run the draft is cleared; on a partial run the published items are removed and the leftovers stay in the draft — with any child of a now-published parent rewritten to point at the real Azure id — so a re-run only creates what's left. Pass -KeepDraft to leave the draft in place regardless. az-Clear-AzDevOpsDraft discards the whole thing.

Items published without a priority set default to Azure's stock Medium (2); a User Story's completeness score also expects story points and acceptance criteria, so the tree flags exactly what's still blank before you commit.

PowerShell-only. Like the rest of the az-New-AzDevOps* create surface, draft mode lives in powcuts_by_cli/azdevops_draft.ps1; .az_bashcuts has only az boards query aliases and no structured create flow to hook into.

Per-org field-schema config

Every Azure DevOps org configures its own required + custom fields via process templates (e.g. a "Customer Impact" required field on every User Story, or a "Compliance Risk" picklist). The schema-management commands let you declare those fields once per org so future schema-aware updates to az-New-AzDevOpsUserStory, az-Get-AzDevOpsAssigned, az-Show-Tree, etc. can prompt for / surface them automatically.

The schema lives at $HOME/.bashcuts-az-devops-app/schema/schema-<org>.json (per-org keyed off $env:AZ_DEVOPS_ORG; falls back to schema.json when unset). The directory is created with 0700 permissions on macOS / Linux; Windows inherits the user-only ACL from %USERPROFILE%.

az-Initialize-AzDevOpsSchema     # introspect your org via
                              #   `az devops invoke --area wit --resource workitemtypes`
                              #   and write a starter schema. Refine afterward.
az-Get-AzDevOpsSchema            # print summary table of every required/optional field
az-Get-AzDevOpsSchema -PassThru  # return objects (pipeable / scriptable)
az-Edit-AzDevOpsSchema           # open the schema in $env:EDITOR / code / notepad / nano
                              #   (creates a stub if the file doesn't exist yet)
az-Test-AzDevOpsSchema           # validate the JSON, that every ref still exists in the
                              #   org, and that picklist options are a subset of
                              #   the org's allowedValues. Verdict: VALID / STALE /
                              #   INVALID with a list of any unknown refs / option
                              #   mismatches.

Schema file format (one entry per work-item type, each with required and optional field arrays):

{
  "User Story": {
    "required": [
      { "name": "Customer Impact", "ref": "Custom.CustomerImpact", "type": "string" },
      { "name": "Compliance Risk", "ref": "Custom.ComplianceRisk", "type": "picklist",
        "options": ["Low","Medium","High"] }
    ],
    "optional": [
      { "name": "Epic Owner Name", "ref": "Custom.EpicOwnerName", "type": "string" }
    ]
  }
}

Supported type values: string, int, picklist, bool, date, multiline. Unknown types are treated as string with a warning from az-Test-AzDevOpsSchema.

Daily viewer (local dashboard)

az-Start-AzDevOpsDailyViewer (in powcuts_by_cli/daily_viewer.ps1, tab-tab on az- to discover it) serves the daily-viewer/ dashboard from your machine so the tiles — a Calendar tile grouping Today's Agenda and Events to Prepare For as collapsible sections, plus This Sprint's Focus, Recent Activity, and Today's Focus (Azure DevOps) — render instantly from a local cache and only hit their live source when you refresh a tile.

The dashboard has two modes, switched with the Agenda / Create tabs below the header. Agenda is the read view described here. Create is the Azure DevOps creation surface, itself split into two sub-tabs — Work item and Draft. Work item files a User Story, Task, Feature, Epic, or a Feature with child Stories from the browser: the form is a function of the type you pick (only the fields that type carries render), area / iteration / parent choices come from the local cache, and a submit runs the same non-interactive az boards work-item create core the terminal az-New-* commands wrap — so the browser and the terminal produce identical work items. Draft is the brain-dump builder (see below). Timer sessions and unplanned-work capture are rolling out across follow-up work. The active mode is reflected in the URL, so #create deep-links straight to it and the back button returns you to Agenda. Create mode checks it can reach the backend on first open via POST /api/create/ping; opened without the local server, it reports "offline — sample mode."

The Draft sub-tab is the browser counterpart of the terminal az-*-AzDevOpsDraft brain-dump flow: assemble an Epic → Feature → User Story → Task hierarchy locally and publish it in one action. It renders the same on-disk draft.json the terminal uses (so a draft you started in the shell shows up in the browser and vice-versa), as a tree with a per-item completeness % — the share of that tier's expected fields (title / description / priority, plus story points + acceptance criteria for a User Story, plus a parent link for any non-Epic) that are filled in, mirroring az-Show-AzDevOpsDraft. Each node carries Edit, + child tier (adds a child of the correct type nested under it), and Remove actions; the add/edit form only offers a parent one tier up that's already in the draft, matching the terminal tier validators. Publish resolves the area / iteration once (from the same cache pickers the create form uses) and walks the tree parents-first through the same create + parent-link core, then shows each created item's id as an escaped link; a clean run clears the draft, a partial run keeps the leftovers. Nothing hits Azure DevOps until you publish. Like the create form, every drafted title reaches the page as text (never markup), and the draft write endpoints are POST + application/json on 127.0.0.1 only.

az-Start-AzDevOpsDailyViewer            # serve on http://127.0.0.1:8770/ and open the browser
az-Start-AzDevOpsDailyViewer -Port 9000 # pick a different loopback port
az-Start-AzDevOpsDailyViewer -NoBrowser # serve without auto-opening the browser (scripted / curl checks)
az-Start-AzDevOpsDailyViewer -Refresh   # force-rebuild every tile at startup, regardless of the daily refresh

The server is a built-in System.Net.HttpListener (no external modules) bound to 127.0.0.1 only — never 0.0.0.0 — and your az login / PAT stays inside the server process; responses carry only work-item and agenda data. Press Ctrl+C to stop it.

The first startup of each calendar day rebuilds every tile before serving, so the dashboard opens on today's real agenda and work instead of yesterday's cache (a small refreshed-on.json stamp beside the tile cache records the day). Same-day restarts skip the rebuild and only fill any missing tile, so they stay instant; pass -Refresh to force a full rebuild on demand. A tile whose source is unavailable during the daily rebuild fails soft — the other tiles still refresh and the server still starts.

Each tile is backed by one JSON file under the active project's cache slice — ~/.bashcuts-az-devops-app/cache/<project-slug>/daily-viewer/{agenda,prep,week,activity,focus}.json (or the unsegmented cache/daily-viewer/ for single-project use) — so it follows az-Use-AzDevOpsProject exactly like the synced datasets. Staleness is derived from each file's modified time and returned to the page. The API keeps the cheap and expensive paths distinct:

  • GET / — the page and its static assets.
  • GET /api/tiles/<name> — that tile's cached JSON, plus its ageSeconds / stale staleness (cheap read).
  • POST /api/tiles/<name>/refresh — re-runs that tile's query, rewrites its cache, and returns the fresh JSON (expensive; per-tile).
  • POST /api/tiles/prep/prep-marker — persists one prep row's "all set" / "prep still needed" marker (body { "id", "marker" }, keyed by the meeting's stable event id) so the choice survives a refresh or reload.
  • POST /api/create/ping — the Create mode reachability smoke check; returns { "ok": true } when the backend is serving.
  • GET /api/create/options — cache-backed picker data for the create forms: the project's area and iteration paths (from areas.json / iterations.json) and the parent candidates per type — Epics, Features, and User Stories — from hierarchy.json. Read-only, so it stays a plain GET; the write actions below are POST + application/json only, so a cross-origin forgery can't reach them.
  • POST /api/create/workitem — creates a work item (and links its parent when one applies) through the non-interactive create core (Invoke-AzDevOpsWorkItemCreate + Invoke-AzDevOpsParentLink), the same helpers az-New-* wrap. Body: { type, title, description, priority, storyPoints?, acceptanceCriteria?, area, iteration, parentId?, stories? } where type is User Story / Task / Feature / Epic / FeatureStories (the last creates a Feature plus the stories[] under it). Returns the new id / url, or { "ok": false, "error": … } on a validation, auth, or az failure so the form can surface it. A successful create is also appended to the local hierarchy.json (the same cache the pickers and az-Show-Tree read), so a Feature you just made from the browser is immediately selectable as a parent in the Story / Task pickers without waiting for the next az-Sync-AzDevOpsCache.
  • GET /api/draft/state — the current draft as a flat item list (each with its ref, type, title, fields, parentRef / parentId, and a computed percent / missing[]) plus count / avgPercent / readyCount. Read-only, cache-only — reuses Read-AzDevOpsDraft, so it stays a plain GET.
  • POST /api/draft/{add,set,remove,clear} — the draft mutations, each reusing the terminal helper of the same name (az-Add-AzDevOpsDraftItem / az-Set-AzDevOpsDraftItem / az-Remove-AzDevOpsDraftItem / az-Clear-AzDevOpsDraft -Force) with params supplied so nothing prompts; a supplied parent is tier-checked against Test-AzDevOpsDraftTypeMatchesTier first. No az call. Each returns { "ok": true, "draft": <state> } (or { "ok": false, "error": … }) so the page repaints in one round-trip.
  • POST /api/draft/publish — the only draft action that touches az: creates the whole hierarchy parents-first, reusing Sort-AzDevOpsDraftForPublish + Build-AzDevOpsDraftCreateArgs + the create/link core + Update-AzDevOpsDraftAfterPublish. Body { area, iteration, keepDraft? }. Returns { published:[{ type, id, url, linked, … }], failed:[…], draft }, or { "ok": false, "error": … } on an auth / validation failure.

Each tile is populated from a real source, reusing the same WIQL defaults and Outlook module the rest of the toolkit uses:

  • Today's Agenda — today's calendar events from ol-Get-OutlookAgenda (desktop Outlook), with the Teams join link when the meeting carries one.
  • Events to Prepare For — the next two weeks of meetings, each row carrying a date, the meeting's start time and location (a Teams join link when the meeting has one, the room otherwise — the same agenda-style detail the Today's Agenda tile shows), and a marker you can toggle between "prep still needed" and "all set." That choice is saved server-side by the meeting's event id (a small prep-markers.json beside the tile cache), so a meeting you mark "all set" stays that way when the cache reloads.
  • This Sprint's Focus — your active current-sprint assigned stories (the assigned WIQL), scoped to the iteration that brackets today (falling back to all active assigned stories when none match).
  • Recent Activity — @-mention discussions (the mentions WIQL), your recent updates (the activity WIQL), and your current-sprint items (the activity rows scoped to the iteration that brackets today). The current-sprint group reads [System.IterationPath] off the activity WIQL; the seeded default already selects it, but if you seeded your activity.wiql before this field was added, open o-az-devops-queries-config-dir, add [System.IterationPath] to the SELECT in activity.wiql, and re-run az-Sync-AzDevOpsCache so the group can populate.
  • Today's Focus — the pinned work item you set in $global:AzDevOpsDailyFocus (a work-item id), plus an "assigned & unplanned support" bucket. Set it in your $profile, e.g. $global:AzDevOpsDailyFocus = 1234; leave it unset and the tile shows the support bucket alone.

Work-item rows carry a priority chip (the [Microsoft.VSTS.Common.Priority] field, rendered P1P4) and, on the assigned-backed tiles, a due-date chip ([Microsoft.VSTS.Scheduling.TargetDate]). The seeded WIQL defaults already select both, but if you seeded your query files before these columns were added they won't populate: open o-az-devops-queries-config-dir, add [Microsoft.VSTS.Common.Priority] to the SELECT in mentions.wiql and activity.wiql and [Microsoft.VSTS.Scheduling.TargetDate] to assigned.wiql, then re-run az-Sync-AzDevOpsCache so the chips fill in. A row with no priority or date set simply omits the chip.

Any tile whose source is unavailable (not logged in to az, Outlook not reachable, or nothing to show) renders a friendly empty state rather than erroring, and the page falls back to sample data when opened directly (without the local server). Opening the viewer off Windows still works — the Outlook-backed agenda simply comes up empty.

On Windows, HttpListener may need a one-time URL reservation the first time you bind a port — if az-Start-AzDevOpsDailyViewer reports it could not bind, run the netsh http add urlacl line it prints (or just pick another -Port).




Timer sessions

Start-TimerSession runs a focus-timer Pomodoro against an item from one of your registered integrations and auto-posts your debrief notes as a discussion comment on the chosen item. The Azure DevOps integration is registered out of the box — it reads your cached assigned items (az-Sync-AzDevOpsCache), shows them sorted by State + Priority, and posts the debrief via az boards work-item update --discussion.

Run a session

# 25-minute default
Start-TimerSession

# Custom duration
Start-TimerSession -Minutes 45

# Skip the integration picker (one registered integration is also auto-selected)
Start-TimerSession -Integration 'Azure DevOps - User Stories'

Flow:

  1. Pick an integration (skipped when -Integration is supplied or only one is registered)
  2. Pick an item from the integration's grid
  3. Countdown runs for $Minutes — WPF circular overlay on Windows, snake animation on macOS/Linux
  4. Capture your debrief (debrief notes, what's next) → comment posted on the picked item

The debrief

On Windows the countdown morphs into a themed debrief form that shares the timer's dark/blue style: one window with a Debrief field and a Next step field plus a Post Debrief button. While the comment posts, the button is replaced by a spinner / Posting... state, and the Start another session? choice only appears once the comment posts successfully — so you always know the debrief landed before deciding whether to loop into a fresh timer. The choice is three buttons: Same item reopens a new countdown on the work item you just debriefed (skipping both the integration and item pickers so you can keep grinding on the same ID), Pick another loops back to the picker, and Done ends the session. Right-click the form to cancel without posting. A failed post keeps the form open with the error so you can retry.

On macOS/Linux the debrief is collected with terminal Read-Host prompts after the snake animation, and a Posting... indicator shows while the comment is sent.

Opening the work item in your browser

When the chosen integration supplies an OpenItem capability, both timer surfaces add a one-click jump to the item in your browser — no need to copy the ID into a separate command. On Windows an Open work item button appears on the countdown overlay (open it mid-session without stopping the clock) and on the debrief form (open it while you write your notes — the form stays put). On macOS/Linux the Start another session? menu gains an [o] Open work item in browser choice that opens the item and re-prompts, so opening never consumes your restart decision. The built-in Azure DevOps integration maps OpenItem to az-Open-WorkItemById; integrations without an OpenItem simply don't show the button or option.

Closing the story when a session finishes the task

When the chosen integration supplies a CloseItem capability, the debrief surface adds a one-click way to transition the work item to its done state right after the comment lands — so a session that actually finished the task doesn't leave the item lingering in Active. On Windows the debrief form renders a Work complete — resolve this story checkbox above Post Debrief; ticking it makes the post sequence run the comment, then the state transition, and reports both outcomes before the Start another session? choice appears. On macOS/Linux the same trigger is a Resolve this item now? [y/N] prompt that follows a successful comment post (default No — your work item is never resolved without an explicit yes).

The built-in Azure DevOps integration maps CloseItem to System.State=Resolved (Agile process: New → Active → Resolved → Closed). Override with a single line in your $profile if your process template uses a different done-state:

$script:TimerCloseState = 'Done'   # Scrum / Basic process

A failed state transition (e.g. an invalid workflow move) is reported distinctly — the comment still lands; the resolve verdict tells you the state update didn't, so you can fix it in the AzDO UI without losing the debrief.

Interrupting a session

Press Esc during the countdown (or use Mark Complete Early on the Windows overlay) to end the session early and still go through the debrief. The posted comment header reflects the outcome:

  • Completed: Pomodoro complete — 25:00
  • Interrupted: Session interrupted at 04:30 of 25:00

On both platforms the Start another session? choice appears after every successful post (completed or interrupted). On macOS/Linux it's a terminal prompt — [s] Same item / [p] Pick another item / [d] Done (blank or d ends the session) — mirroring the Windows buttons: Same item loops straight back to the countdown on the work item you just debriefed, Pick another returns to the picker so you can pivot to a different story / integration without retyping the command. Ctrl-C is still a hard exit — no debrief, no comment.

When the chosen integration is Azure DevOps and you've cached a roster with az-Sync-AzDevOpsTeam, the debrief form shows a "Tag teammates" field (type a name/email, click a suggestion) so you can notify colleagues on the posted comment — see Tagging teammates below. Custom integrations can opt into the same field by registering with -SupportsMentions.

Registering your own integration

Add to your $profile (or any file dot-sourced from it). Registering with an existing Name replaces the prior entry, so you can override the built-in AzDO integration without touching tracked code.

Register-TimerIntegration `
    -Name        'My Tracker' `
    -Description 'Items from my custom tracker' `
    -FetchItems  {
        # Return rows with at least Id, Type, State, Title.
        # Optional: Priority, Iteration, anything else you want in the picker.
        Get-MyTrackerItems | Where-Object { $_.Status -ne 'Done' }
    } `
    -AddComment  {
        param([Parameter(Mandatory)] [int] $Id, [Parameter(Mandatory)] [string] $Body)
        Add-MyTrackerComment -Id $Id -Body $Body
    } `
    -ViewHint    {
        param([Parameter(Mandatory)] [int] $Id)
        "View: my-tracker open $Id"
    } `
    -OpenItem    {
        # Optional. When present, the countdown overlay and debrief form show an
        # "Open work item" button (Windows) and the terminal next-action menu
        # offers `[o] Open work item in browser`.
        param([Parameter(Mandatory)] [int] $Id)
        Open-MyTrackerItem -Id $Id
    } `
    -CloseItem   {
        # Optional. When present, the debrief shows a resolve checkbox
        # (Windows) / `Resolve this item now? [y/N]` prompt (terminal) that
        # fires after a successful comment post.
        param([Parameter(Mandatory)] [int] $Id)
        Set-MyTrackerItemDone -Id $Id
    } `
    -SupportsMentions   # optional; offer the az-Sync-AzDevOpsTeam tag picker and
                        # append AzDO @-mention anchors to the posted comment


Unplanned work sessions

az-Start-UnplannedWork is the free-for-all companion to the Pomodoro timer for firefighting that can't be time-boxed. Each day rolls up under a single Unplanned Work — yyyy-MM-dd User Story; every firefight you start becomes a child Task with its own debrief. PowerShell-only, like the timer (the Windows balloon reminder and key-poll loop have no bash counterpart).

Run a session

# Prompts for the firefight title, reminds every 5 min
az-Start-UnplannedWork

# Skip the title prompt, custom reminder cadence
az-Start-UnplannedWork -Title 'Help Dana with the deploy' -ReminderMinutes 10

# No balloon reminder
az-Start-UnplannedWork -NoReminder

Flow:

  1. Prompts for the firefight title, then starts the session immediately — no waiting on Azure DevOps round-trips up front (the story and Task are created when you stop, at debrief time)
  2. Runs a foreground session — press Space to log a timestamped item, Esc/Q to stop (Windows shows a circular WPF stopwatch overlay; macOS/Linux a terminal counter)
  3. A reminder balloon pops every -ReminderMinutes until you stop
  4. On stop: today's daily Unplanned Work story is found or created (its id is cached for the rest of the day, so the next firefight grabs it instantly) and a child Task for this firefight is created and linked; captured items flush to the Task description, then you're prompted for debrief notes and whether there's an opportunity for a new feature / user story to prevent the firefight in future (it can spin one up via az-New-AzDevOpsUserStory). A single debrief comment (time spent + notes + opportunity) is posted on the Task.

Start three separate firefights in a day and you get three Tasks under the one daily story — exactly the "three different chats, three different efforts" shape.

End-of-day roll-up

# Today
New-UnplannedWorkDebrief

# A past day
New-UnplannedWorkDebrief -Date 2026-05-19

Reads the day's local ledger (kept under the AzDO cache dir so total time can be summed — AzDO doesn't store per-session minutes), prints the per-Task breakdown with total time, and on confirm posts a roll-up comment on the daily story.

Tagging teammates

Every debrief that posts a comment — the Pomodoro timer (Start-TimerSession), the per-firefight az-Start-UnplannedWork stop, and the New-UnplannedWorkDebrief roll-up — can tag teammates with real, notifying Azure DevOps @-mentions. The taggable roster is shared across all three and is built by az-Sync-AzDevOpsTeam:

az-Sync-AzDevOpsTeam          # pick a project team, cache its members for tagging
az-Sync-AzDevOpsTeam -Team 'My Team'   # skip the picker and pull a named team

az-Sync-AzDevOpsTeam pulls a project team's members in one call (az devops team list-member) — each member already comes with name, email, and identity id, so no per-person lookups are needed. If the project has more than one team you'll be asked which to use (or pass -Team). The resolved roster is cached under the AzDO cache dir; re-run the command after switching project/team or changing the supplement below.

To make people outside the picked team taggable, set $env:AZ_TEAM to a ;-separated list of emails (names work too); az-Sync-AzDevOpsTeam resolves each via the identities API and merges them into the cached roster. Commas also work as separators, so avoid them inside a value (use emails, which never contain a comma):

$env:AZ_TEAM = 'contractor@example.com;lead@othergroup.com'
az-Sync-AzDevOpsTeam

Once a roster is cached, each debrief gives you a "Tag teammates" field — there's no yes/no prompt to get past:

  • On Windows (the Pomodoro timer's debrief form, and the per-firefight az-Start-UnplannedWork stop, which reuses the same WPF form) it's a blank text box above the Post button. Start typing a name or email and a small list of matching teammates appears underneath; click one to add it. Added people show on a "Tagged: …" line (click that line to clear). Leave the box empty to tag nobody.
  • On macOS/Linux it's a single blank prompt — Tag teammates (; or , separated names/emails, blank = none). Type one or more teammates (or just press Enter to skip).

Either way the people you pick are added to the posted comment as real @-mentions, so they're notified. Typed entries resolve against the cached roster first and fall back to a live identity lookup, so a name that's slightly outside your synced team still works. Tagging is always optional, and if you've never run az-Sync-AzDevOpsTeam the field doesn't appear at all.

About

Shortcuts using bashrc file

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages