CLI · CI/CD · AI agents

A CLI for people and AI agents

Dedicated subcommands, machine-readable --json, and service-principal or API-key auth — the same /api/v1 contract as the UI. Built for CI pipelines and LLM tool loops.

See it in action

Why it fits automation and AI

One command = one API

Semantic hierarchy (entities, packages, permissions…) — no URL guessing for agents or scripts.

Global --json

HTTP status and body in one JSON object on stdout. Parse with jq — no regex over human text.

Two identity modes

Interactive browser login, or service principal / API key for long-running agents and hands-off CI.

Same contract as the UI

Calls the same /api/v1 — one source of truth for business rules, approvals, and permissions.

Raw api escape hatch

Method, path, query, and body from file when a dedicated subcommand is not enough.

OpenAPI-ready

Server Swagger describes schemas and stable error codes — a clear map for tool-calling agents.

Example workflow

  1. Point the CLI at your EntiHub instance and sign in
  2. Verify identity with whoami --json
  3. Run any command with --json for scripts and agents
# Instance config and identity
entihub config set --base-url https://your-enti-hub/ \
  --tenant-id <tenant> --client-id <app-id>
entihub login

# Verify identity (also useful in scripts after login)
entihub whoami --json

# Agent / CI with service principal: set ENTIHUB_CLIENT_SECRET then e.g.
entihub entities list --json

CI/CD: promote a package

Build an entity package in CI, gate on health and JSON status, deploy to staging or production with a service principal.

  1. Store Entra secrets (or an API key) in your pipeline vault
  2. Export a ZIP package from source (curl for binary ZIP)
  3. Deploy with entihub packages deploy … --json and assert statusCode

Secrets and variables

  • ENTIHUB_BASE_URL — e.g. https://mdm.staging.contoso.com
  • ENTIHUB_TENANT_ID, ENTIHUB_CLIENT_ID, ENTIHUB_CLIENT_SECRET — Entra app with MDM admin roles
  • ENTIHUB_SCOPE — typically api://<your-api-app-id>/.default
  • ENTIHUB_API_KEY — alternative CI auth via X-API-Key

Export options JSON

Commit something like mdm-config/export-package.json — omit entityStorageKeys to export all entities.

{
  "entityStorageKeys": ["Customer", "Vendor", "Region"],
  "autoIncludeDependencies": true,
  "includePermissions": true,
  "includeWebhooks": true,
  "includeSeed": false
}
Show full GitHub Actions workflow
name: Promote MDM package

on:
  push:
    branches: [main]
    paths: ['mdm-config/**', '.github/workflows/mdm-promote.yml']

jobs:
  promote:
    runs-on: ubuntu-latest
    environment: staging-mdm
    steps:
      - uses: actions/checkout@v4

      - name: Install entihub CLI
        run: |
          curl -sSL -o entihub.tgz "https://entihub.com/api/cli/download/ubuntu"
          tar xzf entihub.tgz && sudo mv entihub /usr/local/bin/

      - name: Smoke — API readiness
        env:
          ENTIHUB_BASE_URL: ${{ secrets.ENTIHUB_BASE_URL }}
        run: |
          curl -fsS "$ENTIHUB_BASE_URL/health/ready" | jq -e '.status == "ready" or . == {}'

      - name: Export package (ZIP is binary — use curl -o, not entihub --json)
        env:
          ENTIHUB_BASE_URL: ${{ secrets.ENTIHUB_BASE_URL }}
          ENTIHUB_TENANT_ID: ${{ secrets.ENTIHUB_TENANT_ID }}
          ENTIHUB_CLIENT_ID: ${{ secrets.ENTIHUB_CLIENT_ID }}
          ENTIHUB_CLIENT_SECRET: ${{ secrets.ENTIHUB_CLIENT_SECRET }}
          ENTIHUB_SCOPE: ${{ secrets.ENTIHUB_SCOPE }}
        run: |
          TOKEN=$(curl -sS -X POST "https://login.microsoftonline.com/${ENTIHUB_TENANT_ID}/oauth2/v2.0/token" \
            -H "Content-Type: application/x-www-form-urlencoded" \
            --data-urlencode "client_id=${ENTIHUB_CLIENT_ID}" \
            --data-urlencode "client_secret=${ENTIHUB_CLIENT_SECRET}" \
            --data-urlencode "grant_type=client_credentials" \
            --data-urlencode "scope=${ENTIHUB_SCOPE}" | jq -er .access_token)
          curl -fsS -X POST "${ENTIHUB_BASE_URL}/api/v1/packages/export" \
            -H "Authorization: Bearer ${TOKEN}" \
            -H "Content-Type: application/json" \
            --data-binary @mdm-config/export-package.json \
            -o mdm-package.zip
          file mdm-package.zip | grep -q ZIP

      - name: Export package with API key (alternative to OAuth)
        env:
          ENTIHUB_BASE_URL: ${{ secrets.ENTIHUB_BASE_URL }}
          ENTIHUB_API_KEY: ${{ secrets.ENTIHUB_API_KEY }}
        run: |
          curl -fsS -X POST "${ENTIHUB_BASE_URL}/api/v1/packages/export" \
            -H "X-API-Key: ${ENTIHUB_API_KEY}" \
            -H "Content-Type: application/json" \
            --data-binary @mdm-config/export-package.json \
            -o mdm-package.zip
          file mdm-package.zip | grep -q ZIP

      - name: Deploy package to target
        env:
          ENTIHUB_BASE_URL: ${{ secrets.ENTIHUB_BASE_URL }}
          ENTIHUB_TENANT_ID: ${{ secrets.ENTIHUB_TENANT_ID }}
          ENTIHUB_CLIENT_ID: ${{ secrets.ENTIHUB_CLIENT_ID }}
          ENTIHUB_CLIENT_SECRET: ${{ secrets.ENTIHUB_CLIENT_SECRET }}
        run: |
          entihub packages deploy --file mdm-package.zip \
            --overwrite-existing true \
            --import-permissions true \
            --import-webhooks true \
            --import-seed false \
            --json > deploy-result.json
          jq -e '.statusCode >= 200 and .statusCode < 300' deploy-result.json
          jq '.body' deploy-result.json

      - name: Post-deploy — list entities
        env:
          ENTIHUB_BASE_URL: ${{ secrets.ENTIHUB_BASE_URL }}
          ENTIHUB_TENANT_ID: ${{ secrets.ENTIHUB_TENANT_ID }}
          ENTIHUB_CLIENT_ID: ${{ secrets.ENTIHUB_CLIENT_ID }}
          ENTIHUB_CLIENT_SECRET: ${{ secrets.ENTIHUB_CLIENT_SECRET }}
        run: entihub entities summary --json | jq '.body | fromjson'

packages/deploy returns JSON — use entihub … --json + jq. packages/export returns a ZIP — write bytes with curl -o. Most CLI calls can also use ENTIHUB_API_KEY / --api-key.

AI agent: tool loop

Give the model a closed set of tools, deterministic arguments, and structured results. Map each tool to one entihub call with --json; parse statusCode and body for the next step.

  1. Define tools as one CLI invocation each
  2. Run a multi-step task (discover → sample → act)
  3. Gate writes on human approval or policy

Tool definitions

mdm_list_entities     → entihub entities list --json
mdm_get_entity_yaml   → entihub entities yaml <name> --json
mdm_validate_yaml     → entihub entities parse-yaml --file <path> --json
mdm_deploy_entity     → entihub entities deploy <name> --json
mdm_list_data         → entihub entities data list <name> --take 20 --json
mdm_search_data       → entihub search data --q <terms> [--take N] [--include-refs] --json
mdm_raw_api           → entihub api GET|POST|PUT|DELETE <path> [--body file] [--query k=v]

Multi-step task: find duplicate customers

# Auth option: API key mode (X-API-Key)
export ENTIHUB_API_KEY=<secret>

# Step A — discover entities (agent picks Customer from summary)
entihub entities summary --json > step_a.json

# Step B — fetch sample rows for fuzzy match input
entihub entities data list Customer --take 50 --json > step_b.json

# Step C — call similarity API (file body)
cat > similar-req.json <<'EOF'
{ "values": { "name": "Acme Corp", "code": "ACM-01" } }
EOF
entihub entities data similar Customer --file similar-req.json --json > step_c.json

# Step D — optional write (human-approved or policy-gated)
entihub entities data update Customer <record-guid> --file patch.json --json
Show system prompt fragment
You manage EntiHub via the entihub CLI only. Each tool call must use --json.
After every command, parse stdout as JSON: check statusCode (2xx = success).
The "body" field may be a JSON string or raw text — parse accordingly.
Never invent entity names; call mdm_list_entities first.
For bulk changes, prefer packages or bulk import endpoints; do not loop
hundreds of single-row updates without explicit user approval.
Use mdm_raw_api only when no dedicated subcommand exists.

Wire this to Cursor, Copilot, LangGraph, or any agent runtime: execute the shell command the model emits and feed the JSON result back into the conversation.

Works with any EntiHub tier · same API as the UI · Features · Pricing