Universal CLI Grammar

The Problem with Vendor-Specific Verbs

The current ISE CLI grew organically around ISE’s API surface:

netapi ise mnt sessions
netapi ise get-endpoint AA:BB:CC:DD:EE:FF
netapi ise ers endpoints list
netapi ise dc query "SELECT * FROM radius_auth"

These verbs are tightly coupled to ISE’s concepts. get-endpoint means nothing to GitHub. mnt sessions assumes ISE’s Monitoring persona. Every new vendor would need its own vocabulary, and every user would need to learn it from scratch.

The fix is a universal grammar: one sentence structure that works for any API.

Universal Grammar

netapi <vendor> <resource> <verb> [args] [--flags]
Segment Role

vendor

The API provider: ise, github, cloudflare, pfsense, or any user-defined vendor

resource

The noun you are operating on: endpoint, repo, dns, user

verb

The action: list, get, create, update, delete, count, export

args

Positional arguments the verb requires (an ID, a MAC, a zone name)

--flags

Global or vendor-specific modifiers

The grammar reads like English: netapi github repo list. Subject-object-verb, always in that order.

Verb Vocabulary

Seven standard verbs cover the CRUD lifecycle plus operational needs:

Verb HTTP Equivalent Behavior

list

GET (collection)

Return all resources, paginated. Supports --filter, --limit, --page.

get

GET (single)

Return one resource by ID or unique key.

create

POST

Create a new resource. Accepts --data or --file for the payload.

update

PUT / PATCH

Modify an existing resource. Requires an ID argument.

delete

DELETE

Remove a resource. Requires an ID argument. Honors --dry-run.

count

GET + aggregation

Return only the count, without transferring full payloads.

export

GET (collection) + file write

Stream all resources to a file. Supports --format and --output.

Vendors may register additional verbs (ISE keeps coa for Change of Authorization, for example), but these seven are always available when the resource supports them.

Global Flags

These flags work identically across every vendor:

# Output format — default is table for humans, json for pipes
netapi ise endpoint list --format json
netapi github repo list --format yaml
netapi cloudflare dns list --format csv

# Authentication override
netapi custom get "https://api.example.com/v1/users" --auth bearer

# Write output to file instead of stdout
netapi ise endpoint export --output endpoints.json

# Verbose mode — print request/response headers
netapi github repo get anthropics/claude --verbose

# Dry run — show what would happen without executing writes
netapi ise endpoint delete ABC123 --dry-run
Flag Values Default

--format

json, table, yaml, csv

table when stdout is a TTY, json otherwise

--auth

basic, bearer, oauth2, mtls, api-key

From vendor definition

--output

File path

stdout

--verbose

Boolean

false

--dry-run

Boolean

false

--filter

Key=value expression

None

--limit

Integer

Vendor default (typically 20)

--page

Integer or cursor token

First page

Cross-Vendor Examples

# ISE — list endpoints, filter by group
netapi ise endpoint list --filter "groupId=abc123"

# GitHub — list repos for an org, output as JSON
netapi github repo list --org anthropics --format json

# Cloudflare — list DNS records for a zone
netapi cloudflare dns list --zone example.com

# pfSense — get a specific firewall rule
netapi pfsense rule get 42

# Custom one-off — hit any URL directly
netapi custom get "https://api.example.com/v1/users" --auth bearer

# Chaining — ISE endpoints to CSV for spreadsheet import
netapi ise endpoint list --format csv --output endpoints.csv

# Piping — feed into jq for field extraction
netapi github repo list --org anthropics --format json | jq '.[].full_name'

# Piping — count endpoints matching a pattern
netapi ise endpoint list --format json | jq '[.[] | select(.mac | startswith("AA:BB"))] | length'

Backward Compatibility

Existing ISE commands become aliases. The old form continues to work; the new form is canonical:

Legacy Command Universal Equivalent

netapi ise get-endpoint AA:BB:CC:DD:EE:FF

netapi ise endpoint get AA:BB:CC:DD:EE:FF

netapi ise mnt sessions

netapi ise session list

netapi ise ers endpoints list

netapi ise endpoint list

netapi ise dc query "SELECT …​"

netapi ise dataconnect query "SELECT …​"

Legacy aliases emit a deprecation notice to stderr on first use, directing the user to the new form. This keeps scripts working while nudging toward the standard grammar.

Plugin Architecture

Vendor support loads through a discovery chain:

  1. Built-in vendors — Shipped with netapi. ISE, pfSense, WLC, IOS.

  2. User-defined vendors — YAML definitions in ~/.config/netapi/vendors/. See Vendor Definition Format.

  3. --vendor-file flag — Load a vendor definition from an arbitrary path for testing.

At startup, netapi scans built-in vendors first, then overlays user definitions. If a user definition has the same name as a built-in, the user definition wins — this allows overriding default behavior without forking.

# Validate that a vendor definition loads correctly
netapi vendor validate github

# List all available vendors
netapi vendor list

# Show the resolved definition for a vendor
netapi vendor show ise --format yaml

Output Pipeline Design

Every netapi command produces structured output designed for composition with standard Unix tools:

# JSON output pipes cleanly into jq
netapi ise endpoint list --format json \
  | jq '.[] | {mac: .mac, group: .groupName}'

# CSV output pipes into awk for field extraction
netapi ise endpoint list --format csv \
  | awk -F',' 'NR>1 {print $2}'

# Table output is human-readable but not for parsing
netapi ise endpoint list --format table

When stdout is not a TTY (piped or redirected), netapi defaults to JSON. This means netapi ise endpoint list | jq . works without --format json. When stdout is a TTY, it defaults to table for readability.

The --format flag always overrides auto-detection.