Agent-native CLI for managing Microsoft Fabric artifacts and data.
Documentation · Getting started · CLI reference
Composable by design: commands just work together with JSON output by default, errors that explain how to fix things and what to try next, and results that pipe straight into the next command. Follows the 10 principles for agent-native CLIs.
Everything fabio does is achievable via the public Fabric REST APIs — fabio encodes the operational knowledge so coding agents can skip the trial and error, saving time, tokens, and money.
Microsoft Fabric has two official tools: Fabric CLI (fab — interactive Python CLI) and fabric-cicd (Python CI/CD library). fabio is designed to be a strict superset — everything they do, fabio does too, plus capabilities neither offers.
| Capability | Fabric CLI | fabio |
|---|---|---|
| Design philosophy | Interactive-first (shell with cd/ls) |
Agent-native (non-interactive, structured output) |
| CI/CD deployment | Wraps fabric-cicd library | Native engine with content-hash diffing |
| Default output | Human text | JSON (machine-parseable by default) |
| Item type coverage | ~20 commands | 81 command groups |
| SQL query execution | No | Warehouse, SQL Database, Lakehouse (T-SQL via TDS) |
| Query plan capture | No | Estimated execution plan (SHOWPLAN_XML) without executing |
| Query performance monitoring | No | Running/frequent/long-running queries, kill sessions |
| Statistics management | No | List/show/create/update/delete user-defined statistics |
| KQL query execution | No | KQL Database and Eventhouse queries |
| Data Agent interaction | No | Create, configure, publish, and query Data Agents (staging management API, datasource/fewshot/config CRUD, single-turn query over the agent's MCP endpoint, evaluate batch runs, LLM-judged validate-fewshots and evaluate --llm-* via a bring-your-own judge model) |
| Git integration | No | Full lifecycle: connect, status, commit, pull, switch branch |
| Notebook run | job run with --wait/--timeout |
Run with --wait, --timeout, --parameters, cancel |
| Bulk operations | No | item bulk-create, item bulk-delete (parallel) |
| OneLake sync | No | rsync-like sync with rename detection and dedup |
| Parallel data operations | No | Upload, copy, move, sync with bounded concurrency |
| Error handling | Generic messages | Machine-readable codes with hints and valid enum values |
| Runtime | Python 3.10+ (pip install) | Single Rust binary, no runtime |
| Capability | fabric-cicd | fabio |
|---|---|---|
| Source directory format | .platform directories |
Same (100% compatible) |
| Raw Power BI Desktop PBIP | No (requires .platform per item) |
Deploys <name>.Report/<name>.SemanticModel folders with no .platform (type inferred from folder) |
| Plan before apply | No (always pushes everything) | plan → review → apply |
| Skip unchanged items | No (re-uploads every definition) | SHA-256 content hash (terraform-like) |
| Rename detection | No (delete + create) | Logical ID matching (preserves item GUID) |
| Dry-run mode | No | --dry-run on all mutations |
| Export from workspace | No | deploy export |
| Local validation | No | deploy validate (no API calls needed) |
| Branch-out ID rebinding | No (can't touch Git-synced feature workspaces) | deploy rebind rewrites env IDs in local files offline; deploy validate --pr-ready gates the PR |
| Non-Git-tracked item awareness | Silently skips unsupported types | deploy export flags them in a tracking_note with promotion guidance (see context best-practices item-tracking-categories) |
| Parameter scaffolding | No | deploy init-params (scans/diffs GUIDs automatically) |
| Cross-item ID references on first deploy | Two-phase deploy required ($items queries the live workspace, which is empty on first deploy) |
Single-pass — resolves $items.Type.Name.id mid-run as items are created (no two-phase hack) |
| Variable library value sets | Auto-activate matching env | Same + list-value-sets + activate-value-set CLI |
| Schedule export/apply | No | deploy export includes .schedules; apply creates them |
| Workspace clone (bulk) | No | workspace clone --source → --dest (bulk APIs) |
| Post-deploy orchestration | No | --post-run-item triggers pipeline/notebook after deploy |
| Post-deploy convergence audit | No | apply --verify re-checks applied items converged to source (report-only) |
| Deployment strategy | Single (always deploys all) | --strategy default|bulk|sequential (per-item, bulk API, or serial) |
| Output format | Python logs | JSON envelope (stdout/stderr separation) |
| Item types supported | 27 (deploy only) | 48 (deploy) + 81 command groups (full CRUD, query, run) |
| Selective filtering | Feature-flagged, limited | --exclude-regex, --include-items, --include-folders |
| Runtime | Python 3.9+ (pip install) | Single Rust binary, no runtime |
- AI-native interactions — create, configure, and query Data Agents (public staging management API: datasource CRUD, few-shot management, table selection, element descriptions,
--stagefor draft vs published, reset/publish lifecycle; multi-turnquery,evaluatebatch runs, and LLM-judgedvalidate-fewshots/evaluate --llm-*with a bring-your-own judge model); execute KQL for real-time intelligence, NL-to-KQL translation - Layered agent knowledge — 8 orchestrator personas, 12 architecture blueprints, 7 term disambiguations, and 15 generated intent-scoped sub-skills (see Agent knowledge architecture) route agents to the right workload; every mechanical command index is generated from the CLI schema and CI-drift-checked
- Self-correcting error hints — every error includes a
hintfield with the exact corrected command, valid enum values, or the logical next step so agents can retry without consulting docs - Self-improving — when new Fabric REST APIs are detected, fabio auto-implements support for new commands and item types
- Terraform-like convergence —
10BC8
re-running
deploy applyon a synced workspace produces zero API calls - Single-pass cross-item resolution —
$items.Type.Name.idreferences resolve during a singledeploy apply, even to a brand-new empty workspace, because fabio records each item's freshly-created GUID as it goes (no two-phase deploy — see How fabio avoids the chicken-and-egg problem) - Saved deployment plans —
--out plan.jsonthenapply --plan plan.json(with staleness detection) - Fabric deployment-plan items — author and validate ordered CI/CD orchestration plans offline, preserve their logical identities across generic deployments, and explicitly attach them to Git pull, bulk import, or deployment-pipeline deploy operations. Generic
deploy applystores plans without executing their actions. - Workspace folder management — infers folder hierarchy, creates/moves/deletes automatically
- Workspace recycle-bin lifecycle — list retained soft-deleted items, recover item trees, or permanently delete recoverable items with destructive dry-run signals
- Protected type deletion guards — Lakehouse/Warehouse/Eventhouse safe from accidental deletion
- Post-deploy automation — Semantic Model refresh, Environment publish, SQL endpoint polling, Variable Library value set activation, job schedule creation
- Workspace clone — replicate workspace items via Bulk Export/Import APIs (
--allow-pairing-by-namefor initial clones) - Data orchestration —
--post-run-itemtriggers a pipeline or notebook after deployment to populate lakehouses - Dataflow modernization — assess every Gen1 dataflow for upgrade readiness and migrate 1-50 items to Gen2 in place with per-row results
- Capacity governance — inspect/configure surge protection and overage thresholds, and manage blocked or mission-critical workspace actors with structured dry-run previews and capacity-scoped permission guidance
- Connection ownership visibility — connection lists expose the caller's effective role, including
PendingOwner, in JSON and table output; tenant-admin connection errors point to the Fabric Administrator role - Policy sets — create tenant- or capacity-scoped governance definitions, manage beta policy rules, and activate them with admin-aware safety guardrails
- Git branch governance — control whether members and contributors may change the connected branch, at connect time or through connection settings
- Parallel execution — bounded-concurrency async deployment (default 8 parallel ops per type)
- Profile management — named profiles store default workspace, capacity, output format, and private link settings; switch contexts with
fabio profile use
A Fabric item's definition often references another item — e.g. a Semantic Model's Direct Lake connection or a Notebook's default lakehouse points at a Lakehouse that only gets its real per-workspace GUID once it is created in the target workspace. On a first deploy to an empty workspace that GUID does not exist yet, so the reference cannot be filled in ahead of time. This is the classic "chicken-and-egg" problem of Fabric CI/CD.
In the Fabric Git-integration format, such references are stored as the referenced item's logical ID (the stable, environment-independent config.logicalId in its .platform), not its per-workspace GUID. On deploy, each logical-ID reference must be rewritten to the deployed item's real GUID in the target workspace.
How fabric-cicd handles the raw-GUID case: when a raw GUID must be injected (e.g. a Lakehouse GUID into a Variable Library value set), fabric-cicd's parameter.yml $items.<Type>.<Name>.$id placeholder is resolved by querying the live target workspace before publishing. On a brand-new workspace that query returns nothing, so it fails. The documented workaround is a two-phase deploy — a first run that creates only the dependency items (Lakehouse, Ontology, …), then a second run that resolves their now-existing GUIDs and deploys everything else. You have to split your pipeline into two calls and know which types to seed first.
How fabio handles it: fabio needs no two-phase hack. deploy apply orders creates/updates by a topological item-type priority (DEPLOY_ORDER, aligned 1:1 with fabric-cicd's serial publish order) and, as each item is created, records its freshly-assigned GUID in an in-run logicalId → deployed-GUID map. When a later dependent item is deployed, resolve_logical_ids_in_payload rewrites its logical-ID references using the dependencies already created in the same run — so cross-item references resolve in a single pass, even against a completely empty workspace. The map is also seeded with the GUIDs of pre-existing items being updated/skipped, so references resolve on incremental re-deploys too.
# One command. The Lakehouse is created first (topological order); its new GUID is
# recorded and used to rewrite the Semantic Model / Notebook / Variable Library
# logical-ID references to it later in the SAME run.
fabio deploy apply --source ./export --workspace "Production (empty)" --env prodOnly the
--strategy bulkpath (a single additivebulkImportDefinitionsbatch) cannot resolve these references mid-batch, because every item in the batch is created at once — so it rejects interdependent items withDependenciesCouldNotBeResolved. The default per-item strategy — and therefore the general recommendation for first deploys with cross-item references — resolves them in one pass. (fabric-cicd's$itemsparameter form is a separate construct; fabio resolves cross-item references via Fabric logical IDs.)
- JSON by default -- All commands output structured JSON for machine consumption
- Composable -- Pipe output between commands via stdout/stdin
- Structured errors -- Machine-readable error codes with hints and valid enum values
- Non-interactive -- No prompts; all parameters via flags/env/files
- Safe mutations --
--dry-runfor destructive operations; idempotent where possible - Bounded responses --
--limitfor list commands; concise default output - Async-aware --
--waitfor long-running operations; local job ledger - Discoverable --
fabio context agentprovides a machine-readable command schema;fabio contextprovides a layered agent knowledge base — orchestrator personas, architecture blueprints, term disambiguations, intent-scoped sub-skills, item schemas, workflow recipes, and best practices (all generated from the CLI schema and drift-checked) - Context-aware --
fabio context tenantbuilds a workspace relationship graph for agent memory; exports as OWL RDF/XML or JSON-LD for Fabric Ontology import - Ontology round-trip --
fabio ontology import/exportconverts between OWL (RDF/XML, JSON-LD) and Fabric format; compatible with Ontology Playground - Throttling-aware -- Bulk/batch APIs preferred; parallel execution with rate-limit retry
- Mutation-aware retries -- Generic Fabric JSON submissions do not replay ambiguous gateway failures; selected JSON/job paths retain acknowledgement or uncertainty evidence with non-retriable guidance. Safe reads keep bounded retries. See submission retry safety.
- Destructive-discovery coverage -- Safety metadata is generated from reviewed judgment and checked against the real CLI, including nested commands and flag-conditional modes. Preview notices distinguish selected destructive actions from create-only/no-op plans. See destructive previews.
Linux / macOS (installs to ~/.local/bin):
curl -fsSL https://raw.githubusercontent.com/iemejia/fabio/main/install.sh | bashWindows (PowerShell, installs to %LOCALAPPDATA%\fabio):
irm https://raw.githubusercontent.com/iemejia/fabio/main/install.ps1 | iexTo install a specific release instead of the latest, set FABIO_VERSION first (with or without the v prefix):
$env:FABIO_VERSION = "0.78.0"
irm https://raw.githubusercontent.com/iemejia/fabio/main/install.ps1 | iex
Remove-Item Env:FABIO_VERSIONIf the script is downloaded locally, you can alternatively run .\install.ps1 -Version 0.78.0.
Or download pre-built binaries from the releases page (Linux, macOS, Windows — x64 and arm64).
Via agent skill (for AI agents using GitHub Copilot CLI or compatible platforms):
The fabio agent skill ships in-repo at .agents/skills/fabio/. Install it directly from the main repository:
npx skills add https://github.com/iemejia/fabioOr for agents that read a local skills directory:
git clone https://github.com/iemejia/fabio ~/.agents/skills/fabio-repo
# Skills are at ~/.agents/skills/fabio-repo/.agents/skills/fabio/The root fabio skill covers cross-cutting concerns (install, auth, output envelope, safety). For progressive disclosure, fabio also ships fifteen intent-scoped sub-skills — fabio-lakehouse, fabio-warehouse-sql, fabio-data-engineering, fabio-dataflows, fabio-data-science, fabio-mirroring, fabio-rti-kql, fabio-bi, fabio-app-dev, fabio-ontology, fabio-geospatial, fabio-deploy-cicd, fabio-admin, fabio-migration, fabio-planning — so an agent can load only the workload it needs. Each pairs authored judgment (when to use, gotchas, safety, routing) with a command index generated from fabio's own schema, so they never drift from the CLI.
Docker (multi-arch: amd64 + arm64):
docker pull ghcr.io/iemejia/fabio:latest
docker run --rm ghcr.io/iemejia/fabio --helpUse a specific version:
docker run --rm ghcr.io/iemejia/fabio:0.80.0 workspace listPass credentials via environment variables for non-interactive use:
# Service principal (recommended for CI/CD)
docker run --rm \
-e AZURE_TENANT_ID=<tenant-id> \
-e AZURE_CLIENT_ID=<client-id> \
-e AZURE_CLIENT_SECRET=<client-secret> \
ghcr.io/iemejia/fabio workspace list --output json
# Static access token (when you already have a token)
docker run --rm \
-e FABIO_ACCESS_TOKEN=<token> \
ghcr.io/iemejia/fabio workspace list --output jsonThe Docker image uses a minimal distroless base (~20MB) with no shell or package manager. Authentication options inside the container:
| Method | How |
|---|---|
| Service principal | -e AZURE_TENANT_ID + -e AZURE_CLIENT_ID + -e AZURE_CLIENT_SECRET |
| Static access token | -e FABIO_ACCESS_TOKEN=<token> (for pre-existing tokens) |
| Workload identity (AKS/OIDC) | -e AZURE_TENANT_ID + -e AZURE_CLIENT_ID + -e AZURE_FEDERATED_TOKEN_FILE + volume mount |
| Managed identity | Automatic in Azure compute (Container Apps, ACI, AKS) |
Note: Azure CLI (
az login) credentials do not work inside the container becauseazis not installed. Use service principal or managed identity for containerized workloads.
# 1. Sign in
az login --allow-no-subscriptions
fabio auth login
# 2. Create a workspace and assign compute capacity
fabio workspace create --name "sales-analytics" -o table
fabio workspace assign-capacity --id <workspace-id> --capacity <capacity-id>
# 3. Create a lakehouse for your data
fabio lakehouse create --workspace <workspace-id> --name "SalesLakehouse" -o table
# 4. Upload local CSV files (glob patterns, parallel upload)
fabio lakehouse upload --workspace <ws> --id <lh> --source "data/*.csv" --dest Files/raw/
# 5. Load a CSV into a managed Delta table
fabio lakehouse load-table --workspace <ws> --id <lh> \
--path Files/raw/orders.csv --table orders --mode Overwrite --format Csv
# 6. Check your tables
fabio lakehouse list-tables --workspace <ws> --id <lh> -o table
# 7. Query the data via SQL
fabio warehouse query --workspace <ws> --id <warehouse-id> \
--sql "SELECT country, SUM(revenue) as total FROM dbo.orders GROUP BY country"
# 8. Capture an execution plan without running the query
fabio warehouse plan --workspace <ws> --id <warehouse-id> \
--sql "SELECT * FROM dbo.orders WHERE country = 'US'"
# 9. See what queries are running right now
fabio warehouse queries-running --workspace <ws> --id <warehouse-id>
# 10. Find your most expensive queries
fabio warehouse queries-long-running --workspace <ws> --id <warehouse-id> --top 5
# 11. Discover the schema (tables + columns) over INFORMATION_SCHEMA
fabio warehouse list-tables --workspace <ws> --id <warehouse-id> --schema dbo
fabio warehouse describe-table --workspace <ws> --id <warehouse-id> --table dbo.orders
# 12. Bulk-load files into a table with COPY INTO (append-only; --dry-run to preview the SQL)
fabio warehouse copy-into --workspace <ws> --id <warehouse-id> \
--table dbo.orders --file-type PARQUET \
--source https://acct.dfs.core.windows.net/container/orders/*.parquet
# 13. Run SQL via the remote Fabric DW MCP server (Fabric token — no SQL token / TCP 1433)
fabio warehouse query --workspace <ws> --id <warehouse-id> --via-mcp \
--sql "SELECT TOP 10 * FROM dbo.orders"
# 14. Print the remote MCP server URL for an external MCP client (VS Code, Copilot)
fabio warehouse mcp-url --workspace <ws> --id <warehouse-id>That's it -- from sign-in to queryable Delta tables in 7 commands. Steps 8-14 add execution plans, live query monitoring, schema discovery, COPY INTO bulk load, remote-MCP execution, and the MCP server URL for external agents.
Beyond item CRUD, cosmos-db-database speaks the Cosmos DB NoSQL data plane directly — create containers, bulk-import JSONL, run NoSQL queries, and export (no Azure Cosmos SDK required; auth is Entra ID on the cosmos.azure.com scope, endpoint resolved from the item):
# Create a container (Fabric Cosmos is autoscale-only; fabio sends the autoscale max)
fabio cosmos-db-database create-container --workspace <ws> --id <cosmos-id> \
--container products --partition-key /categoryId
# Bulk-import documents from JSONL (UPSERT by default; partition key auto-derived per doc)
fabio cosmos-db-database import --workspace <ws> --id <cosmos-id> \
--container products --source products.jsonl
# Run a NoSQL query (cross-partition by default; --parameter binds @params)
fabio cosmos-db-database query --workspace <ws> --id <cosmos-id> --container products \
--query-text "SELECT c.id, c.name FROM c WHERE c.currentPrice > @min" --parameter min=100
# Single-document CRUD + export to clean JSONL (strips Cosmos system fields)
fabio cosmos-db-database get-document --workspace <ws> --id <cosmos-id> \
--container products --document-id p1 --partition-key electronics
fabio cosmos-db-database export --workspace <ws> --id <cosmos-id> \
--container products --output-file products_export.jsonldelete-container and delete-document are irreversible — preview with --dry-run first (agents receive a destructive agentNotice). Cosmos DB requires F4+ capacity.
# JSON (default) - structured envelope for agents
fabio workspace list
# {"data":[...],"count":2}
# Table - human-readable columns
fabio workspace list -o table
# Plain - one value per line for shell scripting
fabio workspace list -o plainAll errors are structured JSON on stderr with machine-readable codes:
{"error":{"code":"AUTH_REQUIRED","message":"Not authenticated. Run 'az login' first.","hint":"Run: az login"}}Error codes: AUTH_REQUIRED, FORBIDDEN, NOT_FOUND, CONFLICT, RATE_LIMITED, CAPACITY_INACTIVE, INVALID_INPUT, API_ERROR, TIMEOUT, NETWORK_ERROR
| Flag | Description |
|---|---|
-o, --output |
Output format: json (default), table, plain, csv, tsv |
--json |
Shorthand for --output json |
-q, --query |
JMESPath query expression (see jmespath.org) |
--quiet |
Suppress all stdout output |
-v, --verbose |
Enable HTTP/LRO/auth diagnostic tracing on stderr (for debugging only) |
--force |
Skip confirmation prompts for destructive operations |
--dry-run |
Preview mutations without executing |
--limit |
Limit number of results for list commands |
--all |
Fetch all pages (auto-paginate) |
--continuation-token |
Resume pagination from a previous token |
--profile |
Use a named profile for default settings |
--lro-timeout |
Override default LRO polling timeout (seconds) |
--hard-delete |
Permanently delete (skip recycle bin) -- on item deletes |
--readonly |
Block all mutations at HTTP layer (env: FABIO_READONLY) |
--enable-commands |
Allowlist command groups, comma-separated (env: FABIO_ENABLE_COMMANDS) |
--disable-commands |
Denylist command groups, comma-separated (env: FABIO_DISABLE_COMMANDS) |
--wrap-untrusted |
Wrap user-authored fields with <<<UNTRUSTED>>> markers (env: FABIO_WRAP_UNTRUSTED) |
81 command groups with 860+ subcommands covering the full Fabric REST API surface. Use the built-in introspection to discover commands:
# Compact index of all groups + subcommand names
fabio context agent
# Full details for a specific group (all flags, types, examples)
fabio context agent --group lakehouse
# Token-budget-aware: richest subset that fits within N tokens
fabio context agent --budget 4000
# Deep-dive on one command
fabio context describe lakehouse sync
# Search by keyword
fabio context find "upload"
# Multi-step workflow recipes
fabio context workflow cicd-deploy
# Best practices
fabio context best-practices throttling
# Orchestrator personas — which command groups + workflows to use for a role
fabio context persona data-solution-architect # problem-first: routes an outcome to an architecture blueprint
fabio context persona data-engineer # also: data-scientist, app-developer, bi-developer, rti-engineer, migration-engineer, fabric-admin
# Architecture blueprints — item set + deployment phase + key decisions for a whole solution shape
fabio context blueprint medallion # also: lambda, event-analytics, event-medallion, basic-data-analytics, data-analytics-sql-endpoint, basic-machine-learning-models, sensitive-data-insights, conversational-analytics, app-backend, translytical, semantic-governance
# Resolve an overloaded Fabric term to the right artifact + command group
fabio context disambiguate "materialized view" # also: dataflow, semantic-model, sql-endpoint, mirroring, modelWorkspace Monitoring (preview):
# Create a Monitoring item from the canonical definition part
fabio monitoring create --workspace <WS> --name Monitoring_1 \
--file MonitoringProperties.json
# Exactly one Monitoring item per workspace can collect data at a time
fabio monitoring enable-data-collection --workspace <WS> --id <MONITORING_ID>
# Query Reflex activation records directly with KQL
fabio reflex query-activations --workspace <WS> --id <REFLEX_ID> \
--kql '.show tables'
# Queries remain available in read-only mode; use @file or omit --kql for stdin
fabio --readonly reflex query-activations --workspace <WS> --id <REFLEX_ID> \
--kql @activations.kqlWorkspace inbound External Data Shares policy (ETag round trip):
# Read current policy + etag
fabio workspace get-inbound-external-data-shares-policy --workspace <WS>
# Update safely with optimistic concurrency (preserve quotes in etag)
fabio workspace set-inbound-external-data-shares-policy --workspace <WS> \
--default-action Deny --if-match '"a1b2c3d4"'PowerShell equivalent:
fabio workspace set-inbound-external-data-shares-policy --workspace <WS> --default-action Deny --if-match "`"a1b2c3d4`""fabio ships a layered, generated knowledge base for AI agents — inspired by microsoft/skills-for-fabric's Agents→Skills→Common model, but implemented so that every mechanical index is generated from the CLI's own schema and CI-drift-checked (nothing can fall out of sync with the commands):
- Personas (
fabio context persona <name>) — 8 orchestrators (data-solution-architect,data-engineer,data-scientist,app-developer,bi-developer,rti-engineer,migration-engineer,fabric-admin) that route a role/broad task to the right command groups, workflows, and best-practices, with decision gates and safety guardrails.data-solution-architectis problem-first: it maps a described business outcome to an architecture blueprint. - Blueprints (
fabio context blueprint <name>) — 12 architecture-shape solutions (medallion,lambda,event-analytics,event-medallion,basic-data-analytics,data-analytics-sql-endpoint,basic-machine-learning-models,sensitive-data-insights,conversational-analytics,app-backend,translytical,semantic-governance) — the item set (each tagged with its deployment phase + command group), the key decisions, and the workflows that build it. The fabio-native equivalent of Fabric task flows. - Item capabilities (
fabio context item-capabilities [<type>]) — a per-item-type matrix (creatable,supports_definition,deploy_strategy= content/platform_only/unsupported,deployable_from_definition,deploy_order) so an agent can check what an operation supports before attempting it. Derived entirely at runtime from the CLI's own sources (known types + definition specs +DEPLOY_ORDER+commands.json) — no hand-maintained registry to drift. - Disambiguations (
fabio context disambiguate <term>) — resolve overloaded Fabric terms (materialized-view,dataflow,semantic-model,sql-endpoint,mirroring,model) to the concrete artifact + command group. - Intent-scoped sub-skills — 15 focused
fabio-<workload>skills (see Via agent skill) each pairing authored judgment (when-to-use, MUST/PREFER/AVOID, troubleshooting, safety) with a generated command index. - Workflows & best-practices (
fabio context workflow,fabio context best-practices) — multi-step recipes and cross-cutting operational guidance (throttling, LRO, pagination, deploy parameters, migration API shims, …).
Every Fabric workload command group is covered by a sub-skill family or persona. When you describe a business outcome rather than a Fabric item ("analyze streaming sensor data", "let users ask questions of our sales data", "build a batch pipeline from raw to reports"), start with fabio context persona data-solution-architect — it maps the problem to an architecture blueprint (fabio context blueprint <name>) that enumerates the item set (in deployment order) and the decisions to confirm. For broad multi-step tasks (build a medallion lakehouse, train and serve an ML model, mirror a Snowflake database, migrate from Synapse/Databricks/HDInsight, administer a tenant), start with fabio context persona <name>. Migration recipes ship as workflows (synapse-migration, databricks-migration, hdinsight-migration, pipeline-migration) plus the migration-api-shims best-practice (mssparkutils/dbutils→notebookutils, DBFS/WASB/ADLS→OneLake, Linked Services→Connections).
Fabio supports local user, workload, managed-identity, and pre-existing-token authentication. Fabio does not ship a default Entra application ID because the project does not yet have a stable, long-lived Entra tenant and publisher registration that can be safely operated for every installation. Plain auth login therefore validates the supported Azure CLI credential path.
# Default local login: Azure CLI owns browser/WAM/device-code auth and token refresh
az login --allow-no-subscriptions
fabio auth login
# Customer-owned public client: explicit device code or browser PKCE
fabio auth login --device-code --client-id <PUBLIC_CLIENT_ID>
fabio auth login --browser --client-id <PUBLIC_CLIENT_ID>
# Service principal with client secret (CI/CD, automation)
fabio auth login --service-principal --tenant <TENANT_ID> --client-id <CLIENT_ID> --client-secret <SECRET>
# Service principal with certificate (PEM or PFX)
fabio auth login --service-principal --tenant <TENANT_ID> --client-id <CLIENT_ID> --certificate ./cert.pem
fabio auth login --service-principal --tenant <TENANT_ID> --client-id <CLIENT_ID> --certificate ./cert.pfx --certificate-password <PW>
# Service principal with federated token (OIDC, for GitHub Actions / Azure Pipelines)
fabio auth login --service-principal --tenant <TENANT_ID> --client-id <CLIENT_ID> --federated-token <JWT>
fabio auth login --service-principal --tenant <TENANT_ID> --client-id <CLIENT_ID> --federated-token-file <PATH>
# Windows WAM broker with a customer-owned public client
fabio auth login --wam --client-id <PUBLIC_CLIENT_ID>
# Static access token (Fabric Notebooks, environments with pre-existing tokens)
export FABIO_ACCESS_TOKEN=<token>
# Verify authentication
fabio auth statusSupported credential sources (in priority order):
- Static access token (
FABIO_ACCESS_TOKENenv var — for Fabric Notebooks and pre-existing tokens) - Fabio-managed cache from an explicit public-client or service-principal login
- Environment variables (
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_CLIENT_SECRET) - Managed Identity (when running on Azure)
- Azure CLI (
az login-- default local-user path and supported byazure/login) - Azure Developer CLI (
azd auth login)
Windows-specific features:
- Token cache encrypted with DPAPI (
CryptProtectData, user scope) — matches Azure CLI behavior - WAM broker SSO via
--wamflag — uses Windows OS-level sign-in, no browser needed
Device code, browser PKCE, and WAM require a customer-owned public-client registration. Pass --client-id directly or set FABIO_CLIENT_ID:
export FABIO_CLIENT_ID=<your-app-client-id>
fabio auth login --device-code--client-id takes precedence over FABIO_CLIENT_ID. AZURE_CLIENT_ID is reserved for workload identities and is never treated as an interactive public-client fallback. Plain fabio auth login ignores FABIO_CLIENT_ID and validates Azure CLI.
The helper creates a compatible registration with redirects and delegated permissions for Fabric, Storage, SQL, ARM, Kusto, Graph, and Cosmos. It prints the new client ID and never edits Fabio source:
# Requires az CLI with permission to create app registrations
./scripts/create-fabio-app.sh --name "Fabio CLI" --admin-consentfabio auth logout clears only Fabio's token cache. It does not sign out Azure CLI, managed identity, environment credentials, or Azure Developer CLI; run az logout separately when needed. Legacy Fabio caches without issuing-client metadata can serve an unexpired token for its original scope, but Fabio will not refresh them under a different application ID.
Generate tab-completion scripts for your shell. Completions cover all 81 command groups, 860+ subcommands, and their flags.
# Add to ~/.bashrc
eval "$(fabio completions bash)"# Add to ~/.zshrc
eval "$(fabio completions zsh)"Or, for faster shell startup (generates a static file):
fabio completions zsh > ~/.zfunc/_fabio
# Ensure ~/.zfunc is in your fpath (add to ~/.zshrc before compinit):
# fpath=(~/.zfunc $fpath)fabio completions fish > ~/.config/fish/completions/fabio.fish# Add to your $PROFILE
fabio completions powershell | Out-String | Invoke-ExpressionOr, for persistent completions:
# Generate and save to profile directory
fabio completions powershell > "$HOME\Documents\PowerShell\Completions\fabio.ps1"
# Source in $PROFILE:
# . "$HOME\Documents\PowerShell\Completions\fabio.ps1"fabio completions elvish >> ~/.config/elvish/rc.elvAfter setting up completions, restart your shell or source the configuration file. Then use Tab to complete commands, subcommands, and flags:
fabio lak<Tab> → fabio lakehouse
fabio lakehouse <Tab> → list show create upload ...
fabio lakehouse list --out<Tab> → --output
Before retrying a blocked workload, use the GET-only configuration report:
fabio --wrap-untrusted --readonly workspace diagnostics --id "$WS".
Surge/actor reads are opt-in with --include-surge-protection; missing evidence
stays unknown, and no policy is changed. See the
workspace diagnostics guide.
Confirmed blockers can now yield guarded fix previews in diagnostic nextSteps
and strict error hints. Missing identifiers remain explicit templates; ordinary
capacity-related workload errors suggest inspection without hidden probes. See
remediation hints before
executing a proposed change.
For connection and identity triage, use connection diagnostics --id "$CONNECTION_ID"
or connection diagnostics --workspace "$WS" --item "$ITEM". These bounded GET-only
checks distinguish visible metadata, caller roles (including PendingOwner), and
workspace identity configuration from untested credential health and source
authorization. See the connection diagnostics guide.
Use environment diagnostics for bounded publishing/compute/activation history
and explicitly selected Spark session/application observations. Session-only
mode does not require inventing an environment ID; log reads are metadata-only,
and application telemetry is explicitly not attempt-pinned. Typed state
filters and activation follow distinguish terminal lifecycle state from success.
See Spark diagnostics.
Use semantic-model readiness for bounded model-schema observations, opt-in
selected-table counts, exact refresh-request completion and report dataset
binding. DAX queries work under --readonly; readiness never resubmits work or
changes native/generated/authored binding modes. Review notChecked: successful
queries are not proof of source compatibility, freshness or report rendering.
See BI readiness.
Refresh and generator outputs now retain submission/framing receipts and expose
refreshId only when exact-scope response-header correlation is verified.
Acknowledgement is not completion; missing/conflicting identity or unreadable
acknowledgement bodies never authorize resubmission. Generator framed remains
a legacy acknowledgement flag—inspect the structured framing receipt.
Source checking is integrated with native and generated model creation via
opt-in --check-source, and is independently available as semantic-model check-source. Both use the same bounded metadata engine; non-ready source
findings stop before creation, without switching modes or scanning table rows.
See source preflight.
admin connection-hygiene adds bounded admin metadata/ownership review over
explicit IDs or an opt-in inventory sample. Recency and similar targets are
review signals, not unused-connection proof or a winner selection. Guarded
retirement/continuity previews retain approval and dependency blockers; the
command never applies them. See admin connection review.
For legacy Gen1 dataflows, dataflow plan-upgrade adds bounded workspace
assessment and selected-ID review plans without executing upgrades. Service
readiness is not consumer compatibility, historic-data preservation or approval;
guarded previews remain blocked. See Gen1 upgrade planning.
After separate explicit approval, upgrade-gen1 --allow-in-place-upgrade now
rechecks readiness and submits exactly once, retaining a local scoped receipt.
follow-upgrade observes that receipt without resubmitting or refreshing data.
See Gen1 execution receipts.
Local job receipts use locked, synced, atomically replaced JSONL snapshots.
Damaged records block reads and updates instead of silently losing history;
jobs prune --dry-run previews exact local receipt IDs, and normal pruning keeps
uncertain states. See local receipt storage.
Use fabio context describe <group> <command> for examples of any specific command. Use fabio context workflow <name> for multi-step recipes. Available workflows:
lakehouse-etl-- Lakehouse + notebook + load-table + schedulerti-pipeline-- Eventhouse + KQL DB + EventStream end-to-enddirect-lake-report-- Native Delta-backed model, framing/query verification, and empty report binding; explicit generator/authored alternativescicd-deploy-- Export + plan + apply with content-hash convergencedata-agent-setup-- Create + datasource + few-shots + publish
For BI mode selection, use fabio context best-practices bi-creation or the
BI creation guide. Native empty
reports are distinct from the PBIR visual-authoring workflow; creation alone is
not proof of query readiness.
Use fabio in CI/CD workflows to deploy Fabric artifacts automatically. For the full branch-per-environment pipeline (reusable workflow, environment protection, PR gates, rollback), see the Deploy to Fabric from GitHub Actions guide.
No long-lived secrets. Uses GitHub's OIDC token exchange:
name: Fabric Deploy
on: [push]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Azure Login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
allow-no-subscriptions: true
- name: Install fabio
run: |
ARCH=$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')
curl -fsSL "https://github.com/iemejia/fabio/releases/latest/download/fabio-linux-${ARCH}.tar.gz" \
| tar -xz -C /usr/local/bin
- name: Deploy to Fabric
run: |
fabio deploy plan --source ./fabric-items/ --workspace "Production"
fabio deploy apply --source ./fabric-items/ --workspace "Production"Native OIDC (no Azure CLI): pass GitHub's OIDC token straight to fabio via auth login --federated-token — no azure/login/az dependency in the runner. Configure a federated credential (audience api://AzureADTokenExchange) on the app registration:
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: curl -fsSL https://raw.githubusercontent.com/iemejia/fabio/main/install.sh | bash
- name: Sign in to Fabric with GitHub OIDC
run: |
OIDC_TOKEN=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" | jq -r '.value')
fabio auth login --tenant "${{ secrets.AZURE_TENANT_ID }}" \
--client-id "${{ secrets.AZURE_CLIENT_ID }}" --federated-token "$OIDC_TOKEN"
- run: fabio deploy apply --source ./fabric-items/ --workspace "Production" --env prodSimplest setup -- just set environment variables:
name: Fabric Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install fabio
run: |
ARCH=$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')
curl -fsSL "https://github.com/iemejia/fabio/releases/latest/download/fabio-linux-${ARCH}.tar.gz" \
| tar -xz -C /usr/local/bin
- name: Deploy to Fabric
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
run: |
fabio deploy plan --source ./fabric-items/ --workspace "Production"
fabio deploy apply --source ./fabric-items/ --workspace "Production"Run fabio from inside Microsoft Fabric Notebooks using FABIO_ACCESS_TOKEN. This is the recommended auth method when running fabio within Fabric itself, since az login and device code flows are not available in notebook environments.
import subprocess, os, json
# Get a token from the current Fabric session
token = notebookutils.credentials.getToken("pbi")
# Run fabio with the session token
env = {**os.environ, "FABIO_ACCESS_TOKEN": token}
result = subprocess.run(
["fabio", "workspace", "list", "-o", "json"],
env=env, capture_output=True, text=True
)
workspaces = json.loads(result.stdout)
print(f"Found {workspaces['count']} workspaces")
# Deploy items from a repo cloned into the notebook environment
result = subprocess.run(
["fabio", "deploy", "apply", "--source", "./fabric-items/", "--workspace", workspace_id],
env=env, capture_output=True, text=True
)
print(result.stdout)Note:
FABIO_ACCESS_TOKENis the token for the Fabric scope and, by default, is reused for every other audience (Storage, SQL, ARM, Graph). Since access tokens are audience-scoped and can't be exchanged, commands that reach a different audience need a token minted for that audience. Provide one with a scope-specific env var (each takes precedence overFABIO_ACCESS_TOKENfor its scope, and is only fetched when a command needs it):
Env var Audience Needed for FABIO_ACCESS_TOKENFabric ( api.fabric.microsoft.com)all Fabric REST + deploy FABIO_SQL_ACCESS_TOKENAzure SQL ( database.windows.net)T-SQL/TDS: warehouse/sql-database/sql-endpoint/lakehousequeries + insights,semantic-model generate,digital-twin-builder query,ontology generate(lakehouse)FABIO_STORAGE_ACCESS_TOKENAzure Storage ( storage.azure.com)OneLake file ops, if the Fabric token is not accepted FABIO_ARM_ACCESS_TOKENARM ( management.azure.com)capacity lifecycle FABIO_GRAPH_ACCESS_TOKENMicrosoft Graph label listIn a notebook, obtain each with
notebookutils.credentials.getToken(<resource>)and set the matching var — e.g. for TDS commands:env = {**os.environ, "FABIO_ACCESS_TOKEN": notebookutils.credentials.getToken("pbi"), "FABIO_SQL_ACCESS_TOKEN": notebookutils.credentials.getToken("https://database.windows.net")}If a T-SQL command fails with a login error and only
FABIO_ACCESS_TOKENis set, fabio now tells you to setFABIO_SQL_ACCESS_TOKEN.
When giving agents access to fabio, use the safety flags:
# Read-only mode: blocks all mutations at the HTTP layer
FABIO_READONLY=1 fabio lakehouse list --workspace $WS
# Command allowlist: only these groups are available
FABIO_ENABLE_COMMANDS=workspace,lakehouse,context fabio workspace list
# Combined: agent can only read, only from allowed groups
FABIO_READONLY=1 FABIO_ENABLE_COMMANDS=workspace,lakehouse fabio lakehouse list-tables --workspace $WS --id $LH
# Wrap user-authored fields to prevent prompt injection
FABIO_WRAP_UNTRUSTED=1 fabio item list --workspace $WS
# Output: {"displayName": "<<<UNTRUSTED>>>My Item<<<END_UNTRUSTED>>>", ...}Several commands apply item-type-specific options to individual items during a
bulk/definition operation: deployment-pipeline deploy, git pull,
item bulk-import-definitions, and workspace clone. They share one JSON contract
passed via --item-options — a JSON array of {<identifier>, options} entries,
inline or as @file:
# deploy/clone/git-pull/bulk-import: each entry targets one item by its ID and
# carries a JSON options object. The identifier key is the command's ID field:
# deployment-pipeline deploy → "sourceItemId" (item UUID)
# git pull / bulk-import / clone → "logicalId" (logical GUID)
fabio git pull --workspace $WS \
--item-options '[{"logicalId":"00000000-0000-0000-0000-000000000000","options":{"validateOnly":true}}]'
# Or read the entries from a file (@ prefix):
fabio deployment-pipeline deploy --id $DP --source-stage-id $DEV \
--items '[{"sourceItemId":"<uuid>","itemType":"SemanticModel"}]' \
--item-options @item-options.jsonRules and safety:
- Each identifier must be a canonical 36-character hyphenated UUID (the same format every other ID flag requires); compact/URN/braced forms are rejected locally before any request.
- Each identifier may appear once;
optionsmust be a JSON object. - Options are validated locally first, so a malformed
--item-optionsfails fast (withINVALID_INPUT) without contacting Fabric — even under--dry-run. - Irreversible purge: a semantic-model entry may set
options.allowPurgeData: true, which permits Fabric to permanently purge data incompatible with the new definition. When any entry enables it, the--dry-runpreview is marked"destructive": true(with an agentagentNotice) and carries awarning— review it before re-running without--dry-run. After a purge-enablingsemantic-model create/update-definition, refresh the model (fabio semantic-model refresh) so it reframes over the retained data.
Whole-item definition options (item create/item update-definition --options).
Separately from per-item --item-options, the generic item create and
item update-definition commands accept a single --options object applied to
that one item's definition (inline or @file). For a semantic model this is where
you set allowPurgeData:
# Create a semantic model from a definition envelope, permitting a data purge.
# --definition also accepts @file; ALWAYS preview a purge-enabling call with --dry-run first.
fabio item create --workspace $WS --name SalesModel --type SemanticModel \
--definition @definition.json --options '{"allowPurgeData":true}' --dry-run
# Update an existing item's definition with the same option:
fabio item update-definition --workspace $WS --id $ITEM \
--definition @definition.json --options '{"allowPurgeData":true}' --dry-runA --options object enabling allowPurgeData gets the same treatment as the typed
--allow-purge-data flag: the --dry-run preview is marked "destructive": true
with a warning + agentNotice, and the real success output repeats the warning.
A few T-SQL surface commands validate their inputs locally before calling Fabric
(so a bad value fails fast with INVALID_INPUT):
- Audit predicate (
--predicate-expressiononwarehouse/sql-endpoint/sql-database update-audit-settings): pass only the predicate — a leadingWHEREkeyword is rejected (any non-identifier boundary, e.g.WHERE(...), is caught) — and it is limited to 3,000 characters. Omitting the flag leaves the current predicate unchanged; an explicit empty string removes it. - SQL analytics endpoint refresh (
sql-endpoint refresh-metadata --timeout): the refresh timeout may not exceed 24 hours (across all duration units); the documented default is 15 minutes.
fabio releases frequently. When an AI agent is detected, a successful JSON response may carry an additive updateAvailable object announcing a newer release, the detected install method, and the matching upgrade command — plus an agentNotice reminding the agent to re-run fabio context agent after upgrading (its cached command schema may be stale):
{
"data": { "...": "..." },
"updateAvailable": {
"current": "0.60.0",
"latest": "0.66.0",
"installMethod": "cargo",
"upgradeCommand": "cargo install --git https://github.com/iemejia/fabio.git --force"
}
}The check is passive: it reads a local 24h cache (~/.fabio/version-check.json) and refreshes it in a detached background process — never a network call on the command's own path, and never blocking. The 24h interval is enforced even when a refresh fails (offline or GitHub rate-limited), so fabio makes at most one release-API request per day rather than one per command. The field is additive (present only when an update exists). Disable entirely with FABIO_NO_VERSION_CHECK=1, or keep the cached notice but suppress the background refresher with FABIO_NO_BACKGROUND_REFRESH=1.
Opt-in auto-upgrade. Set FABIO_AUTO_UPGRADE=1 and fabio will self-update when the cached check finds a newer release: it spawns a detached fabio upgrade in the background so the current command is unaffected and the new binary takes effect on the next invocation. The notice then carries "autoUpgrade": "launched" so the agent knows the upgrade is already in flight. It is off by default (silently swapping a binary under a running agent/CI is risky), applies only to standalone installs (cargo users update with cargo install --force, docker with docker pull), is throttled to at most one attempt per hour (a persistently-failing upgrade never loops), works whether or not the caller is an agent, and is disabled by FABIO_NO_VERSION_CHECK / FABIO_NO_BACKGROUND_REFRESH.
The MCP server is read-only by default — mutation tools are hidden unless opted in:
# Default: only read-only tools exposed (366 tools)
fabio mcp serve
# Expose mutations for specific groups only
fabio mcp serve --allow-write --allow-tool "workspace,lakehouse"
# Inspect what tools would be exposed (without starting the server)
fabio mcp serve --list-tools
fabio mcp serve --allow-write --list-toolsAgents can branch on $? without parsing JSON:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error (API_ERROR, INVALID_INPUT) |
| 2 | Usage error (bad syntax) |
| 3 | AUTH_REQUIRED |
| 4 | FORBIDDEN / READONLY_MODE |
| 5 | NOT_FOUND |
| 6 | CONFLICT |
| 7 | RATE_LIMITED / CAPACITY_INACTIVE |
| 8 | TIMEOUT |
| 9 | NETWORK_ERROR |
Fabio can update itself to the latest release:
# Check if a newer version is available
fabio upgrade --check
# Update to the latest release (downloads, verifies SHA256, replaces binary)
fabio upgrade
# Install a specific version
fabio upgrade --target-version 0.23.0upgrade --check / upgrade query GitHub's release API, which throttles
unauthenticated requests to 60/hour per IP (shared CI or NAT egress IPs can
hit this). This is transient and resets automatically — when it happens fabio
returns a structured, retriable RATE_LIMITED error, and retrying after a few
minutes usually succeeds. In CI or automation that hits the limit repeatedly,
set a token to raise it to 5000/hour (honored: GITHUB_TOKEN, GH_TOKEN, or
FABIO_GITHUB_TOKEN; a fine-grained token needs no scopes for a public repo):
export GITHUB_TOKEN=<token>
fabio upgrade --checkgit clone https://github.com/iemejia/fabio.git && cd fabio
# Install from source (requires Rust 1.99.1+)
cargo install --path .
# Build
cargo build
# Run tests (unit + offline integration -- 841 tests)
cargo test
# Run E2E tests (requires live Fabric tenant -- 721 tests)
cargo test -- --ignored
# Lint (pedantic + nursery, zero warnings required)
cargo clippy --tests -- -D warnings
# Format
cargo fmtDeployment/governance live lifecycle tests (deployment-plan items, Beta plan references, capacity actors, admin connections, native BI creation, and pool diagnostics) have a separate test guide with scoped prerequisites and cleanup guarantees. Run selected tests rather than all ignored tests against a shared tenant.
The project uses prek (a fast, Rust-native pre-commit runner) for automated local validation before each commit. Install it and set up the hooks:
# Install prek (Rust-native, no Python needed)
cargo install prek
# Install the git hooks (reads prek.toml)
prek installOnce installed, every git commit automatically runs:
- File hygiene (trailing whitespace, EOF fixer, TOML/YAML validation, merge conflict detection, large file guard)
- Secret detection via gitleaks
cargo fmt -- --check(format verification)cargo clippy --tests -- -D warnings(lint with zero warnings)
Configuration: prek.toml
- GitHub Actions CI runs on 6 targets: x64 + arm64 for Linux, macOS, and Windows
- Release workflow: tag-triggered, builds 5 binaries with SHA256 checksums (Linux x64/arm64, macOS arm64, Windows x64/arm64)
cargo-denychecks for security advisories and license compliance (permissive-only policy)- Dependabot auto-merge for passing dependency updates
- CodeQL and Secret Scanning enabled
- 81 command groups with 860+ subcommands
- 8 orchestrator personas, 12 architecture blueprints, 7 term disambiguations, 15 generated intent-scoped sub-skills, 15 workflow recipes, 17 best-practice topics — the layered agent knowledge base
- 1562 tests (841 unit + 721 offline/E2E integration)
- Zero clippy warnings, zero unsafe code
| OS | Architectures |
|---|---|
| Linux | x86_64, aarch64 |
| macOS | x86_64, arm64 (Apple Silicon) |
| Windows | x86_64, aarch64 |
All platforms are built and tested in CI. Pre-built binaries and Docker images (multi-arch: amd64 + arm64) are published with every release.
MIT