Skip to content

Latest commit

Β 

History

46 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

azform

A form for building Azure CLI commands, right in your terminal.

License CI Latest release Downloads Go version Issues Last commit PRs welcome Stars

Status: early development.

Shell support

azform works through a shell keybinding widget, so support is per-shell, not per-OS.

Shell Status
zsh Supported
bash 4.0+ Supported
bash 3.x Not supported β€” needs bash 4+ for READLINE_LINE
sh / dash / ash Not supported β€” no keybinding mechanism
fish Considering
PowerShell Considering
nushell Considering

macOS ships bash 3.2, so if you choose to use bash instead of zsh, you need a newer bash from a package manager. Linux distributions ship bash 4.4+ (RHEL 8+) or 5.x (Debian, Ubuntu, Fedora, Arch), which work as-is. On an unsupported shell the installer still installs the binary, tells you why the widget was skipped, and leaves your shell profile untouched.


az network public-ip create
Create a public IP address.
──────────────────────────────────────────────────────────────────────────────────────────────────────────
● --nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $PIP β†’ pip  β—‹ --acquire-policy-tokenβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --resource-groupβ€€β€€β€€β€€β€€β€€β€€β€€β€€ $RG β†’ myResourceGroup  β—‹ --change-referenceβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --allocation-methodβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ Static  β—‹ --debug
β—‹ --ddos-protection-modeβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β–Ό  β—‹ --help
β—‹ --ddos-protection-planβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  β—‹ --only-show-errors
β—‹ --dns-nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  ● --outputβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ tsv
β—‹ --dns-name-scopeβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β–Ό  β—‹ --queryβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
β—‹ --edge-zoneβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  β—‹ --subscriptionβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --idle-timeoutβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ 15  β—‹ --verbose
β—‹ --ip-addressβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
β—‹ --ip-tagsβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --locationβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $LOC
β—‹ --public-ip-prefixβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
β—‹ --reverse-fqdnβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --skuβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ StandardV2
β—‹ --tagsβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --tierβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ Regional
● --versionβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ IPv4
● --zoneβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”

──────────────────────────────────────────────────────────────────────────────────────────────────────────
az network public-ip create \
  --name $PIP \
  --resource-group $RG \
  --allocation-method Static \
  --idle-timeout 15 \
  --location $LOC \
  --sku StandardV2 \
  --tier Regional \
  --version IPv4 \
  --output tsv

 Done    Cancel                                                                          Press F1 for help
──────────────────────────────────────────────────────────────────────────────────────────────────────────

What it is

azform is a small terminal companion for the Azure CLI.

You press a key. A form opens under your prompt, listing every parameter the command accepts. Required ones are marked. Parameters with a fixed set of allowed values show that set, so you pick instead of typing. When you're done, the assembled command lands in your prompt β€” ready for you to read, adjust, and run.

It is a way to write az commands. Nothing more than that, and that's the point.

Under the hood. azform is a single Go binary. When you open a form, it shells out to az <command> --help and parses the text β€” the same help you'd read yourself. It attaches as a shell widget (Ctrl-X then A, in zsh and bash 4+), takes over the terminal via /dev/tty while the form is open, and writes the finished command straight into your shell's line buffer on exit. No daemon, no telemetry, no phone-home. Small state β€” drafts, remembered variable bindings β€” lives under ~/.local/state/azform/ as plain files you can delete at any time.

What it is not

  • Not a replacement for the Azure CLI. What comes out is a plain az command. Paste it into a script, a pipeline, a message to a colleague, or a bug report. It reads exactly like the documentation.
  • Not a runner. azform never executes the command you're building. It hands it to you and steps aside β€” you press Enter yourself. It does ask the Azure CLI for help text, and, if you let it, reads lists of your existing resources. It never writes anything to Azure, and it never runs a command you didn't run yourself.
  • Not a wrapper with its own syntax. No new commands to learn, no abstraction over Azure concepts, no leaky translation layer between you and the CLI you already know.
  • Not a background service. It runs when you press the key and exits when you're done. Nothing sits in memory, nothing starts with your machine.
  • Deterministic. No suggestions about what you probably meant, no generated commands, no network calls to anyone but Azure.

Who it's for

People who use the Azure CLI regularly enough to be annoyed by it, but not often enough to have memorized it.

If you know exactly which parameters az storage account create takes and how each value is spelled, you don't need this. If you find yourself opening the documentation in a browser to check whether it's TLS1_2 or TLSv1.2, or running the command three times to discover which parameters were required after all β€” that's the gap this fills.

It assumes you know Azure. It does not assume you know the CLI by heart.

Why I built it

The Azure CLI is excellent at what it was designed for: scripting and automation. Commands are long, explicit, and unambiguous β€” exactly right for a file that runs unattended.

That same design is tiring to type by hand. A single command can take dozens of parameters. Some are required, some aren't, and the only way to find out is to run it and read the error. Values are case-sensitive and inconsistently formatted across services. Tab completion helps with the next token, but it can't show you the shape of the whole command, and it can't tell you what you're still missing.

So the loop becomes: type, run, read error, fix, run again. Sometimes four or five times for one resource.

azform replaces the guessing part of that loop with a form. Everything the command accepts is visible at once, what's required is marked as required, and closed value sets are lists you choose from. The typing is still yours. The remembering isn't.

What it can do

  • Show every parameter of a command in one place, with required ones marked
  • Filter the parameter list as you type β€” names and help text are searched live, so a 100-parameter command like az vm create collapses to one row when you know what you're after
az network public-ip create
Create a public IP address.
──────────────────────────────────────────────────────────────────────────────────────────────────────────
β—‹ --dns-nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
β—‹ --dns-name-scopeβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β–Ό

──────────────────────────────────────────────────────────────────────────────────────────────────────────
/ dnsβ–ˆ
──────────────────────────────────────────────────────────────────────────────────────────────────────────
az network public-ip create \
  --name $PIP \
  --resource-group $RG \
  --allocation-method Static \
  --idle-timeout 15 \
  --location $LOC \
  --sku StandardV2 \
  --tier Regional \
  --version IPv4 \
  --output tsv

 Done    Cancel                                                                          Press F1 for help
──────────────────────────────────────────────────────────────────────────────────────────────────────────
  • Turn fixed value sets into pickable lists, so misspellings stop happening
az network public-ip create
Create a public IP address.
──────────────────────────────────────────────────────────────────────────────────────────────────────────
● --nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $PIP β†’ pip  β—‹ --acquire-policy-tokenβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --resource-groupβ€€β€€β€€β€€β€€β€€β€€β€€β€€ $RG β†’ myResourceGroup  β—‹ --change-referenceβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --allocation-methodβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ Static  β—‹ --debug
β—‹ --ddos-protection-modeβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β–Ό  β—‹ --help
β—‹ --ddos-protection-planβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  β—‹ --only-show-errors
β—‹ --dns-nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  ● --outputβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ tsv
β—‹ --dns-name-scopeβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β–Ό  β—‹ --queryβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β—‹ --edge-zoneβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  β—‹ --subscriptionβ€€β€€β€€β€€β€€β€€β€€β€€β€€β”‚  json                β”‚
● --idle-timeoutβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ 15  β—‹ --verbose              β”‚  jsonc               β”‚
β—‹ --ip-addressβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”                           β”‚  none                β”‚
β—‹ --ip-tagsβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”                           β”‚  table               β”‚
● --locationβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $LOC                           β”‚β–Ά tsv                 β”‚
β—‹ --public-ip-prefixβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”                           β”‚  yaml                β”‚
β—‹ --reverse-fqdnβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”                           β”‚  yamlc               β”‚
● --skuβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ StandardV2                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β—‹ --tagsβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --tierβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ Regional
● --versionβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ IPv4
● --zoneβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”

──────────────────────────────────────────────────────────────────────────────────────────────────────────
az network public-ip create \
  --name $PIP \
  --resource-group $RG \
  --allocation-method Static \
  --idle-timeout 15 \
  --location $LOC \
  --sku StandardV2 \
  --tier Regional \
  --version IPv4 \
  --output tsv

 Done    Cancel                                                                          Press F1 for help
──────────────────────────────────────────────────────────────────────────────────────────────────────────
  • Pick up a command you already started typing and let you finish it in the form
  • Fill fields with shell variables you already have defined, and remember which variable you used for which parameter
  • Warn you before you run a command that references a variable your shell doesn't actually have
  • Open a filtered variable picker from any field (Ctrl-G) to insert $VAR from the current shell session without scrolling through your whole env
az network public-ip create
Create a public IP address.
──────────────────────────────────────────────────────────────────────────────────────────────────────────
● --nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $PIP β†’ pip  β—‹ --acquire-policy-tokenβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --resource-groupβ€€β€€β€€β€€β€€β€€β€€β€€β€€ $RG β†’ myResourceGroup  β—‹ --change-referenceβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ filter: rgβ–ˆ                                                                                            β”‚
β”‚β–Ά RG                                                                                                    β”‚
β”‚                                                                                                        β”‚
β”‚                                                                                                        β”‚
β”‚                                                                                                        β”‚
β”‚                                                                                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
● --allocation-methodβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ Static  β—‹ --debug
β—‹ --ddos-protection-modeβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β–Ό  β—‹ --help
β—‹ --ddos-protection-planβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  β—‹ --only-show-errors
β—‹ --dns-nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  ● --outputβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ tsv
β—‹ --dns-name-scopeβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β–Ό  β—‹ --queryβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
β—‹ --edge-zoneβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”  β—‹ --subscriptionβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --idle-timeoutβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ 15  β—‹ --verbose
β—‹ --ip-addressβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
β—‹ --ip-tagsβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --locationβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $LOC
β—‹ --public-ip-prefixβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
β—‹ --reverse-fqdnβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --skuβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ StandardV2
β—‹ --tagsβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”
● --tierβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ Regional
● --versionβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ IPv4
● --zoneβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ β€”

──────────────────────────────────────────────────────────────────────────────────────────────────────────
az network public-ip create \
  --name $PIP \
  --resource-group $RG \
  --allocation-method Static \
  --idle-timeout 15 \
  --location $LOC \
  --sku StandardV2 \
  --tier Regional \
  --version IPv4 \
  --output tsv

 Done    Cancel                                                                          Press F1 for help
──────────────────────────────────────────────────────────────────────────────────────────────────────────
  • Show live values from your Azure subscription where it makes sense β€” resource groups, locations, existing resources
  • Save named presets, so "a storage account like the one in project X" is one keystroke
  • Give the finished command back as a single line, as a multi-line script block, or on your clipboard

What it can't do

  • Tell you what to build. It shows you the parameters; deciding what belongs in them is your job.
  • Validate against Azure before you run. Some things are only knowable by trying. It catches missing required parameters, wrong enum values, and undefined variables β€” not quota limits, naming conflicts, or permission problems.
  • Work with commands the CLI doesn't document. Everything it knows comes from the CLI's own help output. If a parameter isn't described there, azform can't describe it either.
  • Cover the deep structure of generic update commands. For things like --set properties.encryption.keySource=... you get a plain text field. The shape of a resource's properties isn't something the CLI exposes.
  • Undo anything. It never changes anything in your subscription, so there's nothing to roll back β€” and once you press Enter, you're talking to Azure directly, same as always.

Keyboard

The form is keyboard-only. Press ? or F1 inside it for an overlay listing every binding in the current context.

↑  ↓   k  j       move between parameters
←  β†’   h  l       move between columns (grid layout)
Enter             edit field / open enum popup / confirm
Space             toggle optional parameter on/off (required fields show a hint)
Esc               close popup; from list, cancel and save draft
/                 filter by parameter name and help text
Tab  Shift-Tab    cycle list β†’ Done β†’ Cancel β†’ list
g                 open a popup to set a shell variable (`name=value`, or just `name` to re-declare the current session value) β€” the widget evals the line in your shell after the form closes, so the var persists until you unset it; the var also appears in the Ctrl+G picker for the same session
a                 show all collapsed parameters
v                 cycle value visibility for required params (see below)
Ctrl-G            select $VAR from the buffer list and insert at the cursor
w                 cycle through non-blocking warnings in the footer

Enter on Done is blocked while a blocking validation finding is active β€” the footer names the parameter and the reason. Esc inside a popup closes only the popup, not the whole form; cancelling the selection does not lose what is already filled in.

v β€” value visibility cycle

v is a global display toggle: every required parameter whose value is a shell variable reference participates in the same cycle, and the cycle is purely a view change β€” pressing v never mutates a field's Value, VarValue, or Mode. The cycle advances through three states in the value column:

state 0   $RG                       (just the var reference)
state 1   $RG β†’ myResourceGroup     (default; reference and resolved value)
state 2   myResourceGroup           (just the resolved value)
● --nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $PIP
● --resource-groupβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $RG
● --nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ $PIP β†’ pip
● --resource-groupβ€€β€€β€€β€€β€€β€€β€€β€€β€€ $RG β†’ myResourceGroup
● --nameβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ pip
● --resource-groupβ€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€β€€ myResourceGroup

After state 2, the next press wraps back to state 1, then 0, then 1, and so on. Required fields whose reference does not resolve in the current shell (e.g. you forgot to export $RG) stay red and ignore the cycle β€” there is no resolved value to reveal. Optional parameters and required parameters with a non-var literal value ("myResourceGroup", with no $) render their value normally and also ignore the cycle.

The cycle works identically whether the variable reference came from the env pre-fill, a remembered binding, the shell buffer (az … --resource-group $RG), or a restored draft where the value was saved as the literal text $RG. In all cases the resolved value is looked up from the current shell session at render time; drafts do not have to store a separate "this was a var" flag for the cycle to apply.

Use v to preview what az will actually receive:

  • state 0 answers "which variable did I bind this to?"
  • state 1 is the default, useful while filling out the form
  • state 2 answers "what literal value is about to be substituted?"

Pressing v while in text-edit mode types v into the input; press Esc first to leave the field, then v to cycle.

Privacy

azform runs entirely on your machine and holds nothing on a server. The widget in your shell hands it a list of your current variables; variables whose names match *TOKEN*, *SECRET*, *KEY*, *PASSWORD*, *PASSWD*, or *CREDENTIAL* (case-insensitive) are filtered out before anything is read from disk, so secrets never reach the form's variable picker and never land in shell history through the form's actions.

Drafts and bindings store names of variables, not resolved values β€” --resource-group $RG is remembered as RG, never as the group name itself. The only literal values persisted are enum parameters with a closed value set (such as --sku Standard_LRS), where the alternative would be to re-pick from the list each time.

azform does not handle Azure authentication in any form. Tokens, device-code flows, and credential caches live entirely inside the az binary the form calls. If az is signed out, the form degrades gracefully β€” see Diagnostics below.

Terminals

Reference platform is iTerm2 on macOS. The form needs the terminal to emit standard escape sequences for arrow keys, Home, End, and Page Up/Down; terminals that remap any of those β€” most commonly macOS Terminal.app, which binds Home/End to scrollback by default β€” will need them rebound to "beginning/end of line" for navigation to work.

Kitty, WezTerm, Alacritty, and plain xterm on Linux are expected to work; VS Code's integrated terminal is not recommended because it forwards keys inconsistently. If a key seems dead, the form's ? overlay lists every binding it understood, which usually identifies the missing sequence at a glance.


Install

curl -fsSL https://raw.githubusercontent.com/someson/azform/main/install.sh | sh

One command. No sudo, nothing outside your home directory. The installer asks before touching your shell profile, and install.sh --uninstall removes everything it added.

Requires the Azure CLI to be installed and on your PATH. macOS and Linux for now.

The installer never copies the widget from a checkout β€” it asks the binary for it, so the piped one-liner above works from any directory:

azform shell-init zsh    # prints the zsh widget to stdout
azform shell-init bash   # prints the bash widget (needs bash 4+)

Useful if you manage your dotfiles yourself: source the output from your profile instead of letting the installer edit it. Because the script comes out of the binary, the widget can never be a version behind the azform that reads it.

Update and uninstall

Update by rerunning the install command β€” the same one-liner, no special path:

curl -fsSL https://raw.githubusercontent.com/someson/azform/main/install.sh | sh

Uninstall:

curl -fsSL https://raw.githubusercontent.com/someson/azform/main/install.sh | sh -s -- --uninstall

The binary and the widget are removed; your drafts, bindings, and metadata cache are preserved. Add --purge if you want state gone too. Re-running install.sh does not duplicate the widget block in your shell profile β€” it checks for the markers first.

Diagnostics

azform --doctor prints a summary of the runtime environment: which az is found, where the cache and state directories are, whether az account get-access-token works, and the relevant env-var overrides. Run it first when something is wrong.

azform --doctor                  # environment summary
azform --version                 # azform version, commit, build date
azform --dump-cache "vm create"  # JSON view of cached metadata for one command
azform --parse-help save.txt     # parse a saved `az … --help` file, print the JSON
azform --debug                   # write structured events to <state-dir>/debug.log

State and cache live under standard XDG-style paths β€” plain JSON files you can cat and delete:

macOS Linux
cache ~/Library/Caches/azform/ ~/.cache/azform/
state ~/Library/Application Support/azform/ ~/.local/state/azform/

The cache directory holds per-command metadata (commands/*.json). The state directory holds drafts.json (form state from cancelled forms, 20 entries, 7-day TTL), bindings.json (remembered parameter-to-var links), and parse-health.log (rolling log of parser self-diagnostics, last 200 entries). With --debug, debug.log appears next to them.

If a parser change does not seem to take effect on a command you were already editing, the cached metadata is the usual suspect β€” the form will keep showing what was parsed last time. Delete the specific file under commands/ (or the whole cache directory) and reopen the form.

Support me

azform is free, open source, and built in my own time. It will stay that way.

If it saves you a few trips to the documentation, here's what helps, in order of how much it actually matters:

  • Tell me what broke. Bug reports with the command you were building are worth more than anything else on this list. The Azure CLI is large and its help output is not perfectly uniform β€” the parser will hit cases I never saw.
  • Tell me what's missing. Especially if you gave up and typed the command by hand anyway. That's the most useful signal there is.
  • Star the repo if you find it useful. It's how other people find it.

No paid tier, no telemetry, no account required. If that ever changes, it will be announced here first and the current feature set will remain free.

Development

Requires Go 1.26+.

make build       # build ./bin/azform
make install     # go install into $HOME/.local/bin
make test        # go test ./...
make test-race   # go test -race ./...
make lint        # golangci-lint (requires `brew install golangci-lint`)
make lint-fix    # auto-fix goimports and other fixable issues
make help        # list all targets

Lint config lives in .golangci.yml and is tuned for signal-first. make lint must return 0 issues before a PR is ready.

License

MIT β€” see LICENSE. Third-party license texts ship with each release.


Built for the terminal. macOS and Linux first; Windows support is planned.

About

Build Azure CLI commands in a terminal form. Required parameters marked, allowed values pickable, result lands in your prompt.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages