Skip to content

Repository files navigation

azctl

CI Go Report Card Go Version License: MIT

A Go CLI for on-prem Azure DevOps Server (e.g. https://devops.example.com/devops). Microsoft's az devops extension officially supports the cloud only; azctl fills the gap for self-hosted servers and is built for automation from the ground up — every command emits a stable JSON envelope, every flag and output schema is discoverable at runtime, and exit codes map cleanly to failure classes.

Features

  • On-prem first. No cloud dependency; talks to ADO Server REST api-version=7.1 with automatic 6.0 fallback.
  • Scriptable. --json on every command returns a versioned envelope (schemaVersion=1) with embedded, discoverable JSON Schemas.
  • Self-describing. azctl describe emits a machine-readable catalog of commands, flags, and output schemas — the contract for agents and scripts.
  • Safe by default. Mutations prompt for confirmation; --yes opts out for automation.
  • Stable exit codes. Distinct codes for auth, not-found, conflict, network, and server failures.
  • Secret-aware. PAT resolved from stdin, env, or the OS keychain — never written to config files.
  • Zero-config auth flow. azctl login bootstraps a profile, PAT, and connectivity check in one step.

Contents

Install

Build from source (Go 1.26+):

git clone https://github.com/cyteger/azctl && cd azctl
make build
install -m 0755 bin/azctl ~/bin/azctl   # or: make install
azctl --version

Cross-compiled archives (darwin/linux/windows, amd64/arm64) are produced by make release into dist/ along with SHA256SUMS.

Quickstart

# 1. Configure server + collection (one-time)
mkdir -p ~/.config/azctl
cat > ~/.config/azctl/config.toml <<EOF
server     = "https://devops.example.com/devops"
collection = "DefaultCollection"
project    = "SampleProject"
EOF

# 2. Authenticate — paste your PAT (stored in OS keychain)
azctl auth login

# 3. Confirm
azctl auth status

# 4. List your active PRs in a repo
azctl pr list --repo mobile-app --status active

Commands

Run azctl <group> --help for flags, or azctl describe for the full machine-readable catalog.

Group Command Description
auth login Authenticate and (optionally) store a PAT in the keychain
logout Remove the stored PAT for the current server
status Show server, user, and PAT source
pr list List pull requests in a repository
view Show a PR, optionally with comments and commits
create Create a new pull request
complete Complete (merge) a pull request
abandon Abandon a pull request
vote Vote on a PR as the authenticated user
comment Post a comment (new thread or reply)
reviewer Manage PR reviewers
diff Summarize file changes in a PR
repo list List repositories
view Show a repository
branch Branch operations (list, view)
pipeline list List pipelines
view Show a pipeline
run Queue a new run of a pipeline
builds List builds with optional filters
build Show a single build
logs List or stream build logs
profile add Add or update a profile
list List all configured profiles
show Show one profile (defaults to the active profile)
use Set the default profile
remove Remove a profile
describe Emit the JSON catalog of commands, flags, and schemas
login Interactive bootstrap: profile + PAT + verification

Agent mode

Every command accepts --json and returns a stable envelope. Use azctl describe --json to enumerate commands, flags, and output schemas:

azctl describe --json | jq '.commands[].path'
azctl describe --schema pr-list           # raw JSON schema for pr list
azctl pr list --repo mobile-app --json | jq '.data[].pullRequestId'

Schemas covered by describe:

  • auth-status
  • pr-list, pr-view, pr-create, pr-complete, pr-abandon, pr-vote, pr-comment, pr-reviewer, pr-diff
  • repo-list, repo-view, repo-branch-list, repo-branch-view
  • pipeline-list, pipeline-view, pipeline-run, pipeline-builds, pipeline-build, pipeline-logs

Config file

Location: $XDG_CONFIG_HOME/azctl/config.toml, defaulting to ~/.config/azctl/config.toml. Override with --config <path>.

# Global defaults
server     = "https://devops.example.com/devops"
collection = "DefaultCollection"
project    = "SampleProject"
apiVersion = "7.1"                 # optional; otherwise probed

# Named profiles, selectable via --profile <name>
[profiles.staging]
server  = "https://devops-staging.example.com/devops"
project = "SampleProject"

[profiles.legacy]
server     = "https://old-tfs.example.com/tfs"
collection = "LegacyCollection"

Per-command flags (--server, --collection, --project) always override profile and config values.

Environment variables

Variable Purpose
ADO_PAT Personal Access Token. When set, bypasses the OS keychain entirely
XDG_CONFIG_HOME Base directory for azctl/config.toml

The PAT is resolved in this order: --pat-stdin (read from stdin), then ADO_PAT, then the OS keychain entry written by azctl auth login / azctl login.

Automation / CI

The OS keychain ties access to a binary's code signature, so rebuilding (go run, go build, make build) produces a new binary identity and macOS prompts to re-authorize on every invocation. For scripts, CI jobs, or any non-interactive use, export the PAT once and the keychain is never queried:

export ADO_PAT='your-pat-here'
azctl pr list --repo mobile-app         # no prompt
azctl pr list --all --json | jq ...     # no prompt

Pair with --yes to skip confirmation prompts on mutating commands:

azctl --yes pr complete 1234 --repo mobile-app --merge squash

For an installed binary at a stable path (e.g. ~/bin/azctl via make install), macOS' "Always Allow" sticks across runs — the prompt only returns when the binary is rebuilt or moved.

Global flags

  • --server, --collection, --project, --profile
  • --json — emit JSON envelopes on stdout
  • --yes — skip confirmation prompts on destructive commands
  • --debug — log every HTTP request to stderr (PAT redacted)
  • --timeout <sec> — HTTP timeout, default 30
  • --pat-stdin — read PAT from stdin instead of keychain/env
  • --config <path> — alternate config file

Exit codes

Code Meaning When
0 success command completed
1 usage / generic bad flags, missing required values, unclassified
2 auth failed 401 / invalid PAT
3 not found 404 on PR, repo, branch, pipeline, build, etc.
4 conflict 409 — e.g. PR already completed
5 api error other 4xx
6 network dial/TLS failure, context timeout
7 server error 5xx after retries

An error envelope mirrors these codes via its code field (USAGE, AUTH_FAILED, NOT_FOUND, CONFLICT, API_ERROR, NETWORK, SERVER_ERROR).

Known limitations

  • pr diff returns a file-level summary (additions/deletions per path). Inline hunks/blob content are not yet streamed.
  • pipeline logs is fetched once at command time; there is no live tail/poll loop yet.
  • On headless Linux with no D-Bus / libsecret available, the keychain backend may fail to initialize. Fall back to ADO_PAT or --pat-stdin.
  • The api-version probe is implemented in the client but is not yet wired into an azctl auth probe subcommand; servers that reject 7.1 need apiVersion = "6.0" set in config until that lands.

Development

make vet           # go vet ./...
make test          # go test ./...
make test-race     # go test -race ./...
make lint          # golangci-lint run (config in .golangci.yml)
make smoke         # tagged integration tests against a real server
make release       # cross-compile dist/ + SHA256SUMS

Design notes and rationale live in docs/plans/2026-05-14-azctl-design.md.

Contributing

Contributions are welcome. See CONTRIBUTING.md for the build, test, and commit conventions, and CODE_OF_CONDUCT.md for community expectations.

Security

azctl handles Personal Access Tokens. If you find a vulnerability, please follow the disclosure process in SECURITY.md rather than opening a public issue.

License

MIT — see LICENSE.

About

Scriptable Go CLI for on-prem Azure DevOps Server — pull requests, repos, pipelines and builds, with stable JSON output, discoverable schemas, and clean exit codes.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages