Skip to content

cyteger/azctl

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

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors