diff --git a/.gitignore b/.gitignore
index b5e961c8b..5dd953a3f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
/metadata/languages.json
-
###############
# folder #
###############
@@ -21,6 +20,9 @@ artifacts/
target/
/_site/
+# Generated by gen_redirects.py at build time from docfx-template.json
+/docfx.json
+
# Localized content
/localizedContent/en/
/localizedContent/*/docfx.json
@@ -31,6 +33,7 @@ target/
.vscode/
.vs/
+.claude/
###############
# temp file #
diff --git a/content/assets/images/features/cli/cli-command-ls.png b/content/assets/images/features/cli/cli-command-ls.png
new file mode 100644
index 000000000..f27f4b5a9
Binary files /dev/null and b/content/assets/images/features/cli/cli-command-ls.png differ
diff --git a/content/assets/images/features/cli/cli-interactive-mode.png b/content/assets/images/features/cli/cli-interactive-mode.png
new file mode 100644
index 000000000..1e20b63f0
Binary files /dev/null and b/content/assets/images/features/cli/cli-interactive-mode.png differ
diff --git a/content/assets/images/features/cli/cli-preview-notice.png b/content/assets/images/features/cli/cli-preview-notice.png
new file mode 100644
index 000000000..edbfc4f70
Binary files /dev/null and b/content/assets/images/features/cli/cli-preview-notice.png differ
diff --git a/content/features/Command-line-Options.md b/content/features/Command-line-Options.md
index be87a94bc..d46cb9677 100644
--- a/content/features/Command-line-Options.md
+++ b/content/features/Command-line-Options.md
@@ -1,6 +1,6 @@
----
+---
uid: command-line-options
-title: Command Line
+title: Command Line (Tabular Editor 2)
author: Daniel Otykier
updated: 2021-08-26
applies_to:
@@ -10,7 +10,10 @@ applies_to:
- product: Tabular Editor 3
none: true
---
-# Command Line
+# Command Line (Tabular Editor 2)
+
+> [!TIP]
+> Looking for the new cross-platform CLI? See @te-cli for the Tabular Editor CLI (Limited Public Preview), a successor that runs on Windows, macOS, and Linux.
Tabular Editor can be executed from the command-line to perform various tasks, which may be useful in Automated Build and Deployment scenarios, etc.
diff --git a/content/features/csharp-scripts.md b/content/features/csharp-scripts.md
index 51b84fd8a..dc66612b3 100644
--- a/content/features/csharp-scripts.md
+++ b/content/features/csharp-scripts.md
@@ -2,7 +2,7 @@
uid: csharp-scripts
title: C# Scripts
author: Daniel Otykier
-updated: 2026-03-19
+updated: 2026-05-27
applies_to:
products:
- product: Tabular Editor 2
@@ -15,6 +15,8 @@ applies_to:
full: true
- edition: Enterprise
full: true
+ - product: Tabular Editor CLI
+ full: true
---
# C# Scripts
@@ -366,18 +368,27 @@ Info($"Configured model for {environment} environment");
## Compatibility
-The scripting APIs for Tabular Editor 2 and Tabular Editor 3 are mostly compatible, however, there are cases where you want to conditionally compile code depending on which version you're using. For this, you can use preprocessor directives, which were introduced in Tabular Editor 3.10.0.
+The scripting APIs for Tabular Editor 2, Tabular Editor 3 (Desktop), and the Tabular Editor CLI are mostly compatible, but there are cases where you want to conditionally compile code depending on which host is running. The CLI host defines a `TECLI` preprocessor symbol; TE3 Desktop defines `TE3` (and version-bracketed symbols like `TE3_3_15_OR_GREATER` for the active minor); TE2 defines neither. Preprocessor directives were introduced in Tabular Editor 3.10.0. Use them to write portable scripts:
```csharp
-#if TE3
- // This code will only be compiled when the script is running in TE3 (version 3.10.0 or newer).
- Info("Hello from TE3!");
+#if TECLI
+ // CLI host - no UI APIs available
+ Info($"Running under the CLI on {Environment.OSVersion.Platform}");
+#elif TE3
+ // TE3 Desktop - UI APIs are available
+ ShowMessage("Hello from TE3");
#else
- // This code will be compiled in all other cases.
- Info("Hello from TE2!");
+ // TE2 (legacy) - neither TECLI nor TE3 is defined
+ Info("Hello from TE2");
+#endif
+
+#if TE3_3_15_OR_GREATER
+ // Gated on a specific TE3 minor version
#endif
```
+One CLI-specific caveat: the TE3-Desktop UI helpers `SelectMeasure()`, `SelectTable()`, `SelectColumn()`, `SelectObject()`, and `SelectObjects()` throw `NotSupportedException` under `te script` since the CLI has no UI to pop up. Wrap such calls in `#if TE3` (or `try/catch`) when sharing scripts across hosts.
+
If you need to know the exact version of Tabular Editor at script runtime, you can inspect the assembly version:
```csharp
diff --git a/content/features/te-cli/includes/te-cli-preview-notice.md b/content/features/te-cli/includes/te-cli-preview-notice.md
new file mode 100644
index 000000000..791c29254
--- /dev/null
+++ b/content/features/te-cli/includes/te-cli-preview-notice.md
@@ -0,0 +1,2 @@
+> [!IMPORTANT]
+> The Tabular Editor CLI is in **Limited Public Preview**. It is offered for evaluation with a Tabular Editor account; no license is required during preview. Commands, flags, and outputs may change before general availability. **The preview build stops functioning after 2026-09-30.** We recommend against using the CLI in production CI/CD pipelines during preview. Please refer to our license agreement.
diff --git a/content/features/te-cli/te-cli-auth.md b/content/features/te-cli/te-cli-auth.md
new file mode 100644
index 000000000..b8c3704f8
--- /dev/null
+++ b/content/features/te-cli/te-cli-auth.md
@@ -0,0 +1,225 @@
+---
+uid: te-cli-auth
+title: Authentication and Connections
+author: Peer Grønnerup
+updated: 2026-05-06
+applies_to:
+ products:
+ - product: Tabular Editor 2
+ none: true
+ - product: Tabular Editor 3
+ none: true
+ - product: Tabular Editor CLI
+ full: true
+---
+# Authentication and Connections
+
+[!INCLUDE [te-cli-preview-notice](includes/te-cli-preview-notice.md)]
+
+The Tabular Editor CLI authenticates to Power BI Service, Microsoft Fabric, and Azure Analysis Services using the same Power BI Desktop client ID that Tabular Editor 3 uses. Tokens are cached locally so you authenticate once and re-run commands silently until the refresh token expires (typically 90 days).
+
+## Authentication methods
+
+The CLI supports the full Azure Identity credential chain:
+
+| Method | When to use | `--auth` value |
+| -- | -- | -- |
+| Interactive browser | Local development - opens the system browser | `interactive` (default) |
+| Service principal (client secret) | Automation, CI/CD, headless / SSH / WSL | `spn` (with `-u / -p / -t`) or `env` |
+| Service principal (certificate) | Automation with certificate-based auth | `spn` (with `-u / -t / --certificate`) |
+| Environment variables | `AZURE_CLIENT_ID` / `AZURE_CLIENT_SECRET` / `AZURE_TENANT_ID` | `env` |
+| Managed identity | Azure VMs, Azure Container Apps, Azure Functions | `managed-identity` |
+
+> [!NOTE]
+> `--auth` is a **global** option, available on every `te` command - not just `te auth login`. Pass it to [`te deploy`](xref:te-cli-commands#deploy), [`te refresh`](xref:te-cli-commands#refresh), [`te query`](xref:te-cli-commands#query), [`te connect`](xref:te-cli-commands#connect), or any other command that connects to a remote endpoint, to override the default chain for that invocation. The default (`auto`) tries environment credentials first, then falls back to the cached or interactive browser login.
+
+For headless, SSH, WSL, or devcontainer scenarios, use a service principal - `te auth login -u -p -t ` (or `--certificate`). The login is cached, so subsequent commands acquire tokens silently with `--auth auto`.
+
+## `te auth login`
+
+Authenticate and cache the result for subsequent commands:
+
+```bash
+# Browser-based interactive login (default)
+te auth login
+
+# Service principal with client secret
+te auth login -u "$AZURE_CLIENT_ID" -p "$AZURE_CLIENT_SECRET" -t "$AZURE_TENANT_ID"
+
+# Service principal - read secret from stdin
+echo "$AZURE_CLIENT_SECRET" | te auth login -u "$AZURE_CLIENT_ID" -p - -t "$AZURE_TENANT_ID"
+
+# Service principal with certificate
+te auth login -u "$AZURE_CLIENT_ID" -t "$AZURE_TENANT_ID" --certificate ./sp.pfx --certificate-password "$CERT_PASSWORD"
+
+# Managed identity (Azure-hosted)
+te auth login --identity
+```
+
+After a successful service-principal login the CLI **caches the credentials** so every subsequent `te` command can acquire tokens silently - no need to re-pass `-u / -p / -t` or set the `AZURE_CLIENT_*` environment variables. Pass `--save=false` for a one-shot login that doesn't update the cache, or run `te auth logout` to clear it.
+
+> [!WARNING]
+> Passing secrets directly on the command line exposes them to process listings and shell history. Prefer the `AZURE_CLIENT_SECRET` environment variable, or pipe the secret via stdin with `-p -`.
+
+## `te auth status`
+
+Display the current authentication state without opening a browser:
+
+```bash
+te auth status
+te auth status --output-format json
+```
+
+This returns an exit code of `0` when a valid session exists, `1` when not logged in or expired.
+
+## `te auth logout`
+
+Clear all cached credentials:
+
+```bash
+te auth logout
+```
+
+## Credential storage
+
+The CLI stores access/refresh tokens and service-principal records in the **OS-native secure store** by default. A `0600` file fallback is selected automatically only when the OS keystore is unavailable (e.g., headless Linux without libsecret/D-Bus).
+
+| Platform | Backend | Storage location |
+| -- | -- | -- |
+| Windows | DPAPI | Per-user, managed by MSAL |
+| Linux | libsecret (system keyring) | Per-user, managed by MSAL |
+| macOS | Keychain | Service `com.tabulareditor.cli.*`, account `te-msal-cache.bin` |
+| Any (fallback) | `0600` file | `~/.te-cli/te-msal-cache.bin` and per-key `.bin` blobs |
+
+Interactive browser and service-principal flows share the same cache; MSAL's account model distinguishes them - there are no separate `auth-record*.json` sidecar files. Run any command with `--debug` to see which backend was selected at startup.
+
+`te auth logout` clears every cached record (both the MSAL token cache and any SPN blobs) regardless of which backend is in use.
+
+## `te connect` - set the active connection
+
+`te connect` persists an active connection for the current terminal session. Subsequent commands that take `-s` / `-d` can omit them:
+
+```bash
+# Remote workspace
+te connect my-workspace my-model
+
+# Local TMDL folder, .bim file, or .SemanticModel container
+te connect ./my-model
+
+# Connect to a running Power BI Desktop instance (Windows only)
+te connect --local
+
+# Show the active connection
+te connect
+
+# Clear the active connection (and any workspace mirror)
+te connect --clear
+```
+
+Active-connection state is per-terminal-session: opening a new terminal starts fresh.
+
+### Workspace mode (`-w` / `--workspace`)
+
+`te connect -w ` pairs a primary source with a secondary mirror so every subsequent `--save` writes to both. Use it to keep a local working copy of a remote model in sync, or to push local edits to a workspace as you save:
+
+```bash
+# Mirror remote workspace ↔ local TMDL folder
+te connect Finance "Revenue Model" -w ./revenue-model
+
+# Mirror local source ↔ remote workspace (initial deploy + auto-redeploy on save)
+te connect ./revenue-model -w Finance "Revenue Model"
+```
+
+Save order is always **local first, then remote**, so the on-disk copy reflects the latest user change even if the server push fails. See @te-cli-commands#workspace-mode-w--workspace for `--workspace-format`, overwrite semantics, and clearing the mirror.
+
+## Connecting to different clouds
+
+The CLI detects the correct scope from the server URL for:
+
+- Power BI Service and Fabric (commercial, US Gov, China, Germany clouds)
+- Azure Analysis Services (`asazure://...`)
+- Local SSAS (`localhost`, named instances - Windows only)
+
+Pass an XMLA endpoint, workspace name, or `powerbi://` URL as `--server`:
+
+```bash
+te connect "powerbi://api.powerbi.com/v1.0/myorg/Finance" "Revenue Model"
+te connect "powerbi://api.powerbi.com/v1.0/SpaceParts/Finance" "Revenue Model"
+te connect "asazure://westeurope.asazure.windows.net/myaas" "MyModel"
+te connect localhost "AdventureWorks"
+```
+
+## Connection profiles
+
+For repeated use of the same connection - especially when you deploy to multiple environments - save named profiles:
+
+```bash
+# Save remote and local profiles
+te profile set prod -s my-workspace -d my-model --description "Production"
+te profile set dev --model ./model --description "Local dev TMDL"
+
+# List and inspect
+te profile list
+te profile show prod
+
+# Use a profile as the active connection
+te connect --profile prod
+
+# One-shot use without changing the active connection
+te deploy ./model --profile staging --force
+```
+
+Profiles can also carry behavioral overrides that take effect whenever the profile is active:
+
+```bash
+# In dev, disable the BPA gate on deploy and loosen validation
+te profile set dev --bpa-on-deploy false --validate-on-mutation false
+
+# In prod, force auto-format before any mutation
+te profile set prod --auto-format true
+```
+
+See @te-cli-config for the full list of overridable behaviors.
+
+## Non-interactive authentication
+
+For CI/CD pipelines, agents, or any unattended context, avoid interactive flows by combining:
+
+- The `--non-interactive` global flag (fails fast instead of prompting).
+- One of the non-interactive auth methods: `env`, `managed-identity`, or explicit service principal credentials.
+
+Environment-based example for a pipeline:
+
+```bash
+export AZURE_CLIENT_ID="your-app-id"
+export AZURE_CLIENT_SECRET="your-client-secret"
+export AZURE_TENANT_ID="your-tenant-id"
+
+te deploy ./model -s my-workspace -d my-model \
+ --auth env \
+ --non-interactive \
+ --force \
+ --ci github
+```
+
+See @te-cli-cicd for complete GitHub Actions and Azure DevOps Pipelines examples.
+
+## Authentication environment variables
+
+The CLI honors the standard Azure.Identity environment variables when you use `--auth env` (and as part of the `auto` chain):
+
+| Variable | Purpose |
+| -- | -- |
+| `AZURE_CLIENT_ID` | Service principal application ID. |
+| `AZURE_CLIENT_SECRET` | Service principal client secret. Used together with `AZURE_CLIENT_ID` and `AZURE_TENANT_ID`. |
+| `AZURE_TENANT_ID` | Service principal tenant (directory) ID. |
+| `AZURE_CLIENT_CERTIFICATE_PATH` | Path to a PEM or PKCS12 certificate file for certificate-based service principal auth. Used together with `AZURE_CLIENT_ID` and `AZURE_TENANT_ID`. |
+| `AZURE_AUTHORITY_HOST` | Override the authority host for sovereign clouds (e.g., `login.microsoftonline.us`, `login.partner.microsoftonline.cn`, `login.microsoftonline.de`). Defaults to the commercial cloud. |
+
+For CLI-specific environment variables (config paths, debug logging, TE2 compatibility), see @te-cli-config.
+
+## Next steps
+
+- @te-cli-commands - what you can do once connected.
+- @te-cli-config - configuration and profile behavior.
+- @te-cli-cicd - pipeline examples using service principals and managed identity.
diff --git a/content/features/te-cli/te-cli-automation.md b/content/features/te-cli/te-cli-automation.md
new file mode 100644
index 000000000..d5023a3f3
--- /dev/null
+++ b/content/features/te-cli/te-cli-automation.md
@@ -0,0 +1,192 @@
+---
+uid: te-cli-automation
+title: Automation and Scripting
+author: Peer Grønnerup
+updated: 2026-05-06
+applies_to:
+ products:
+ - product: Tabular Editor 2
+ none: true
+ - product: Tabular Editor 3
+ none: true
+ - product: Tabular Editor CLI
+ full: true
+---
+# Automation and Scripting
+
+[!INCLUDE [te-cli-preview-notice](includes/te-cli-preview-notice.md)]
+
+The Tabular Editor CLI is composable; every command supports structured output, disables interactive prompts on demand, and returns predictable exit codes. The same primitives work equally well for shell pipelines, Python scripts, PowerShell automation, and agent-driven workflows.
+
+## Structured output
+
+Use `--output-format` to switch any command between text (human-readable) and machine-readable formats:
+
+| Format | Use for | Notes |
+| -- | -- | -- |
+| `text` (default) | Human-readable use | Plain text on stdout regardless of whether the stream is a TTY or piped. |
+| `json` | Machine-readable use | Always valid JSON to stdout. Use `--error-format json` if you also want machine-readable errors on stderr. |
+| `csv` | Tabular results (`query`, `bpa run`, `bpa rules`, `vertipaq`, `validate`, `test`, `refresh`, `profile list`, `session list`, `find`, `replace`, `get`, `ls`) | RFC 4180 escaping. |
+| `tmsl` (alias `bim`) | Whole-object TMSL/BIM serialization | Accepted by `te get` and `te ls`. |
+| `tmdl` | Whole-object TMDL serialization | Accepted by `te get` only (single object). |
+
+```bash
+te ls --output-format json
+te query -q "EVALUATE VALUES('Date'[Year])" --output-format csv
+te bpa run --output-format json
+```
+
+> [!NOTE]
+> `--output-format` and `--error-format` are independent. Setting `--output-format json` does *not* switch stderr to JSON; pass `--error-format json` for that. There is no automatic format switching when stdout is redirected - the default is always `text` unless you ask otherwise.
+
+## Non-interactive mode
+
+Add `--non-interactive` to any command to disable confirmation prompts, credential picklists, and guided wizards. If the command needs input it cannot resolve from flags, environment, or config, it exits non-zero with an actionable error instead of hanging.
+
+```bash
+te deploy ./model --non-interactive --force --ci github
+```
+
+## Exit codes
+
+Every `te` command exits with a predictable status code so callers can branch on success or failure without parsing stdout.
+
+| Exit | Meaning |
+| -- | -- |
+| `0` | Success. |
+| `1` | Generic failure - invalid arguments, command failed, validation errors, auth failure, BPA gate failed at severity ≥ error. |
+| `2` | Used by `te diff` to indicate models differ (distinct from `0` identical and non-zero errors). |
+
+Combine exit codes with `--ci ` annotations and `--trx ` to surface rich failure information in CI - see @te-cli-cicd.
+
+## Errors on stderr
+
+Errors, warnings, and the preview banner are written to **stderr**; structured data is written to **stdout**. This means you can pipe JSON safely without it being contaminated by progress indicators or diagnostic messages:
+
+```bash
+te ls --output-format json | jq '.[] | .name'
+te vertipaq --output-format json > stats.json
+```
+
+## Python
+
+Python is a natural host for orchestrating CLI calls from data pipelines, notebooks, or test harnesses. Invoke `te` with `subprocess.run`, request JSON, and parse stdout:
+
+```python
+import json
+import subprocess
+
+def query(server: str, database: str, dax: str) -> list[dict]:
+ result = subprocess.run(
+ ["te", "query",
+ "-s", server,
+ "-d", database,
+ "-q", dax,
+ "--output-format", "json",
+ "--non-interactive"],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+ return json.loads(result.stdout)
+
+rows = query("Finance", "Revenue Model", "EVALUATE TOPN(10, 'Sales')")
+for row in rows:
+ print(row)
+```
+
+To capture structured errors from stderr:
+
+```python
+import json
+import subprocess
+
+result = subprocess.run(
+ ["te", "deploy", "./model",
+ "-s", "Finance", "-d", "Revenue",
+ "--output-format", "json", "--non-interactive", "--force"],
+ capture_output=True, text=True,
+)
+
+if result.returncode != 0:
+ try:
+ err = json.loads(result.stderr.strip().splitlines()[-1])
+ print("Deploy failed:", err.get("error"), "- hint:", err.get("hint"))
+ except json.JSONDecodeError:
+ print("Deploy failed:\n", result.stderr)
+```
+
+## PowerShell
+
+PowerShell handles JSON natively. `te` is a regular console binary that works directly in PowerShell pipelines (see @te-cli-migrate if you're porting from the older `TabularEditor.exe` CLI):
+
+```powershell
+$rows = te query -s Finance -d Revenue -q "EVALUATE TOPN(10, 'Sales')" --output-format json --non-interactive
+ | ConvertFrom-Json
+
+$rows | Format-Table
+
+# Check exit code after the pipeline
+if ($LASTEXITCODE -ne 0) {
+ Write-Error "Query failed with exit $LASTEXITCODE"
+ exit $LASTEXITCODE
+}
+```
+
+Read secrets from the environment rather than passing them as plaintext:
+
+```powershell
+$env:AZURE_CLIENT_ID = "your-app-id"
+$env:AZURE_CLIENT_SECRET = "your-client-secret"
+$env:AZURE_TENANT_ID = "your-tenant-id"
+
+te deploy ./model `
+ -s my-workspace -d my-model `
+ --auth env --non-interactive --force --ci vsts
+```
+
+## Bash
+
+Compose commands with pipes and `jq`. The CLI's text output is colorized for humans, but switching to `--output-format json` gives you a clean shape to work with:
+
+```bash
+# Count measures per table
+te ls --type measure --output-format json \
+ | jq -r '.[] | .table' \
+ | sort | uniq -c | sort -rn
+```
+
+```bash
+# Fail the shell script if BPA finds any errors
+te bpa run --fail-on error --output-format json > bpa.json \
+ || { echo "BPA gate failed"; jq '.violations' bpa.json; exit 1; }
+```
+
+## Composability example
+
+Generating a refresh TMSL script and version-controlling it is three commands:
+
+```bash
+te connect MyWorkspace MyModel
+te refresh --type full --dry-run > refresh.tmsl
+cat refresh.tmsl
+```
+
+The resulting TMSL can be reviewed in a pull request, committed, executed by the CLI (`te refresh --type full`), handed to a DBA, or applied by any XMLA-compatible tool. The CLI becomes a building block rather than a black box.
+
+## Useful patterns
+
+A handful of small idioms that come up often when composing `te` commands in scripts or pipelines:
+
+- **Idempotent creates and removes.** `te add Sales/Marker -t Measure -i "0" --if-not-exists --save` and `te rm Sales/OldMeasure --if-exists --save` both exit `0` whether or not the object existed - safe to re-run in CI.
+- **Dry-run diffs.** `te replace` is dry-run by default; add `--save` only when you're satisfied with the preview.
+- **Emit TMSL for review.** `te deploy ./model --xmla deploy.tmsl` produces the deployment script without touching the server - useful for DBA review or manual apply.
+- **Path-only output.** `te ls --paths-only` and `te find --paths-only` emit one object path per line, ideal for piping to `xargs`, `te get`, or `te set`. The model-level containers (`te ls Measures`, `te ls Columns`) compose well with this for whole-model sweeps.
+- **Benchmarking queries.** `te query --trace --cold --runs 5` runs a DAX query with cold cache, five iterations, and captures FE/SE trace events.
+- **Step timings in CI logs.** Long-running commands (`te deploy`, `te refresh`, `te script`, `te validate`) include a `durationMs` field in JSON output - useful for surfacing per-step timings in pipeline summaries.
+
+## Related pages
+
+- @te-cli-cicd - pipeline-specific patterns and YAML examples.
+- @te-cli-commands - full command reference.
+- @te-cli-interactive - when interactive mode fits better than scripting.
diff --git a/content/features/te-cli/te-cli-cicd.md b/content/features/te-cli/te-cli-cicd.md
new file mode 100644
index 000000000..7d7f6b670
--- /dev/null
+++ b/content/features/te-cli/te-cli-cicd.md
@@ -0,0 +1,234 @@
+---
+uid: te-cli-cicd
+title: CI/CD Integration
+author: Peer Grønnerup
+updated: 2026-05-06
+applies_to:
+ products:
+ - product: Tabular Editor 2
+ none: true
+ - product: Tabular Editor 3
+ none: true
+ - product: Tabular Editor CLI
+ full: true
+---
+# CI/CD Integration
+
+[!INCLUDE [te-cli-preview-notice](includes/te-cli-preview-notice.md)]
+
+The Tabular Editor CLI is designed for unattended execution in continuous integration and delivery pipelines. A single binary, structured output, non-interactive mode, native CI annotations for GitHub Actions and Azure DevOps, and VSTEST-compatible test results make it a natural replacement for ad-hoc TE2 invocations.
+
+> [!WARNING]
+> **Do not use the CLI in production pipelines during Limited Public Preview.** Two preview-specific risks apply to pipeline owners:
+>
+> - **Hard expiry.** The preview binary stops functioning on **2026-09-30** - any pipeline depending on it will fail on that date, regardless of your release calendar.
+> - **No backwards-compatibility guarantee.** Commands, flags, output shapes, and exit codes may change between preview builds, so pipeline steps may need updating when you refresh the vendored binary.
+>
+> Build and evaluate in non-production pipelines, and share feedback in the public [TabularEditor/CLI](https://github.com/TabularEditor/CLI) repository so the GA version matches your needs.
+
+## What makes the CLI CI-friendly
+
+- **Single self-contained binary.** No runtime install, no `TabularEditor.exe`, no `start /wait`.
+- **`--non-interactive` global flag.** Disables every prompt; fails fast with actionable errors.
+- **`--force`** on mutating commands (`te deploy`, `te refresh`) skips confirmation prompts.
+- **`--ci vsts` / `--ci github`.** Emit native pipeline annotations to stderr.
+- **`--trx `.** Produce VSTEST results consumable by Azure DevOps test publishing.
+- **Structured errors.** `--output-format json` emits `{"error": "...", "hint": "..."}` to stderr so pipeline steps can fail with a useful message.
+
+## Adding the CLI to your repo
+
+During Limited Public Preview, the CLI is gated behind sign-in on [tabulareditor.com](https://tabulareditor.com/download-tabular-editor-cli), so pipelines cannot fetch the archive from a public URL. The simplest reproducible approach is to commit the binary that matches your runner into your repository and reference it from each pipeline step.
+
+A common layout:
+
+```
+your-repo/
+└── tools/
+ └── te/
+ ├── te # Linux / macOS binary (needs chmod +x at runtime)
+ └── te.exe # Windows binary
+```
+
+Place the **extracted** binary - not the archive - so the pipeline can call it directly. Pick the build that matches your runner OS/arch; see @te-cli-install for the filename table. The self-contained binary is ~70 MB; consider Git LFS if your repo is sensitive to size.
+
+> [!NOTE]
+> Committing the binary also pins the CLI version to whatever you checked in, which is desirable for CI reproducibility. To upgrade, replace the binary in `tools/te/` and commit it - the commit message is your version log. Keep in mind that the preview binary still expires on **2026-09-30** regardless of when you committed it, so a vendored copy is not a permanent dependency - plan to refresh it (and re-validate your pipeline against the new API surface) on preview-build cadence.
+
+## GitHub Actions
+
+A complete deploy + test workflow. The example assumes the Linux `te` binary is committed at `tools/te/te`, and a service principal is stored in repository secrets (`AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID`).
+
+```yaml
+name: Deploy semantic model
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ env:
+ AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
+ AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
+ AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Tabular Editor CLI
+ run: |
+ chmod +x ./tools/te/te
+ echo "$GITHUB_WORKSPACE/tools/te" >> $GITHUB_PATH
+
+ - name: Validate
+ run: te validate ./model --ci github --trx validate.trx
+
+ - name: Best Practice Analyzer (gate)
+ run: te bpa run ./model --fail-on error --ci github --trx bpa.trx
+
+ - name: Deploy
+ run: |
+ te deploy ./model \
+ -s "${{ vars.WORKSPACE }}" \
+ -d "${{ vars.MODEL }}" \
+ --auth env \
+ --non-interactive \
+ --force \
+ --ci github
+
+ - name: Regression tests
+ run: |
+ te test run \
+ -s "${{ vars.WORKSPACE }}" \
+ -d "${{ vars.MODEL }}" \
+ --auth env --non-interactive \
+ --ci github --trx tests.trx
+
+ - name: Publish test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: trx-results
+ path: '*.trx'
+```
+
+## Azure DevOps Pipelines
+
+The Azure DevOps Pipelines equivalent of the GitHub Actions workflow above. The example assumes `te.exe` is committed at `tools\te\te.exe`. `--ci vsts` emits `##vso[...]` commands that the pipeline interprets as errors, warnings, and task-status updates.
+
+```yaml
+trigger:
+ - main
+
+pool:
+ vmImage: 'windows-latest'
+
+variables:
+ - group: 'te-cli-secrets' # Contains AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID
+
+steps:
+ - checkout: self
+
+ - powershell: Write-Host "##vso[task.prependpath]$(Build.SourcesDirectory)\tools\te"
+ displayName: 'Set up Tabular Editor CLI'
+
+ - script: te validate ./model --ci vsts --trx validate.trx
+ displayName: 'Validate'
+
+ - script: te bpa run ./model --fail-on error --ci vsts --trx bpa.trx
+ displayName: 'BPA gate'
+
+ - script: |
+ te deploy ./model ^
+ -s "$(WORKSPACE)" -d "$(MODEL)" ^
+ --auth env --non-interactive --force --ci vsts
+ displayName: 'Deploy'
+ env:
+ AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
+ AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
+ AZURE_TENANT_ID: $(AZURE_TENANT_ID)
+
+ - script: te test run -s "$(WORKSPACE)" -d "$(MODEL)" --auth env --non-interactive --ci vsts --trx tests.trx
+ displayName: 'Regression tests'
+ env:
+ AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
+ AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
+ AZURE_TENANT_ID: $(AZURE_TENANT_ID)
+
+ - task: PublishTestResults@2
+ condition: always()
+ inputs:
+ testResultsFormat: 'VSTest'
+ testResultsFiles: '*.trx'
+```
+
+## BPA gate patterns
+
+`te deploy` and `te save` run the Best Practice Analyzer as a pre-flight gate by default. Three behaviors are worth determining up-front:
+
+- **Enforce** - the default. Pipeline fails if BPA finds violations at severity ≥ error. Pair with `--fail-on warning` on a standalone `te bpa run` step if you want warnings to fail too.
+- **Auto-fix** - `--fix-bpa` applies `fixExpression`s in memory for the deployed artifact. Source files are not modified. Useful when the source of truth lives in the model and you want deploys to normalize style without developer intervention.
+- **Bypass** - `--skip-bpa` disables the gate for a single command. Useful for emergency hotfixes; not recommended as a default.
+
+```bash
+# Treat warnings as failures in PR validation
+te bpa run ./model --fail-on warning --ci github --trx bpa.trx
+
+# Auto-fix during deploy (source unchanged)
+te deploy ./model -s my-ws -d my-model --fix-bpa --force --ci github
+
+# Emergency bypass
+te deploy ./model -s my-ws -d my-model --skip-bpa --force --ci github
+```
+
+See @te-cli-config for controlling the BPA gate globally via `bpa.onDeploy` / `bpa.onSave` config keys.
+
+## Refresh patterns
+
+Refresh in pipelines is typically a follow-up step after deployment. Use `--non-interactive` and pick a deterministic `--type`:
+
+```bash
+# Full refresh of the whole model after deploy
+te refresh -s my-ws -d my-model --type full --non-interactive
+
+# Refresh a single fact table (e.g., daily incremental pipeline)
+te refresh -s my-ws -d my-model --table Sales --type full --non-interactive
+
+# Recalculate only (useful after calculation-group changes)
+te refresh -s my-ws -d my-model --type calculate --non-interactive
+```
+
+For incremental refresh workflows, combine `--apply-refresh-policy`, `--effective-date `, and `--partition ` flags. See @te-cli-commands for details.
+
+## Artifact patterns
+
+Emit TMSL or XMLA as an artifact without deploying, so DBAs or a later job can review or apply it:
+
+```bash
+# Produce the XMLA/TMSL script that would deploy - do not deploy
+te deploy ./model -s my-ws -d my-model --xmla deploy.tmsl --force
+
+# Produce the TMSL refresh command - do not execute
+te refresh -s my-ws -d my-model --type full --dry-run > refresh.tmsl
+```
+
+Commit these artifacts to git, upload them to the pipeline's artifact storage, or pass them between jobs. They're plain text and diff cleanly in pull requests.
+
+## Secret handling
+
+| Approach | When to use | Notes |
+| -- | -- | -- |
+| Service principal via env vars (`AZURE_CLIENT_ID` / `AZURE_CLIENT_SECRET` / `AZURE_TENANT_ID`, `--auth env`) | General CI/CD | Map pipeline secrets to environment variables at the step or job level. Never pass secrets in command arguments. |
+| Service principal via `te auth login` once per job (`echo $SECRET \| te auth login -u $ID -p - -t $TENANT`) | Multi-step jobs | The login is cached, so subsequent `te` commands acquire tokens silently - no need to set `AZURE_CLIENT_*` for every step or re-pass `-u/-p/-t`. Pipe the secret via stdin rather than interpolating it. |
+| Managed identity (`--auth managed-identity`) | Azure VMs, Container Apps, Azure Functions | No secrets to manage. Preferred in Azure-hosted environments. |
+| Certificate (`--certificate `) | Enterprise scenarios with cert rotation | Mount the certificate as a secure file step; pass `--certificate-password` via env. |
+
+> [!WARNING]
+> Do not echo secrets or the output of `te auth status` to pipeline logs. The CLI writes warnings to stderr when secrets are passed on the command line - respect those warnings in CI.
+
+## Related pages
+
+- @te-cli-auth - authentication methods in detail.
+- @te-cli-config - configuration and profile overrides.
+- @te-cli-automation - general scripting patterns.
+- @te-cli-migrate - migrating an existing TE2-based pipeline.
diff --git a/content/features/te-cli/te-cli-commands.md b/content/features/te-cli/te-cli-commands.md
new file mode 100644
index 000000000..50eb3f17e
--- /dev/null
+++ b/content/features/te-cli/te-cli-commands.md
@@ -0,0 +1,869 @@
+---
+uid: te-cli-commands
+title: Command Reference
+author: Peer Grønnerup
+updated: 2026-05-12
+applies_to:
+ products:
+ - product: Tabular Editor 2
+ none: true
+ - product: Tabular Editor 3
+ none: true
+ - product: Tabular Editor CLI
+ full: true
+---
+# Command Reference
+
+[!INCLUDE [te-cli-preview-notice](includes/te-cli-preview-notice.md)]
+
+This page gives a short description and one example per command. Every command accepts `--help` for exhaustive flag documentation:
+
+```bash
+te deploy --help # Help for a single command
+te bpa run --help # Help for a command with subcommands
+```
+
+> [!NOTE]
+> During preview, the CLI's `--help` output is the authoritative reference for flags and options. The content on this page is hand-curated and will lag `--help` for anything added between preview releases.
+
+## Object paths
+
+Object addressing in the CLI uses a single grammar that's shared across every command. Two flavours of path appear in the reference below:
+
+- **``** - resolves to **exactly one** object or container. Used by commands that operate on a single target: `te get`, `te set`, `te add`, `te rm`, `te mv`, `te format -p`, `te deps`, `te macro run --on`.
+- **``** - resolves to **zero or more** objects, with wildcard support. Used by commands that operate on a set: `te ls`, `te bpa run --path`, and other inspection-style commands.
+
+Both path forms share the same syntax rules; they differ in only two places:
+
+- Filter paths allow `*` wildcards; object paths do not.
+- Object paths allow DAX bracket-suffix (e.g. `Sales[Amount]`); filter paths do not.
+
+### Segments and separators
+
+A path is a slash-separated sequence of **segments**. Each segment names a single step - a table, a child object, or a container keyword.
+
+- `Sales` — one segment
+- `Sales/Revenue` — two segments
+- `Roles/Admin/Members/bob` — four segments
+
+Empty input and `.` both mean "the model root" - the implicit starting point for filter paths and the explicit subject for `te get .`-style queries.
+
+### Quoting
+
+Most segment names work as-is. Quote a segment when its name contains spaces, slashes, brackets, or any character that would otherwise be parsed as syntax. The CLI follows DAX quoting conventions, so quoting in `te` paths matches what you'd type inside a DAX expression:
+
+| Form | Use for | Escape rule |
+| -- | -- | -- |
+| `'Net Sales'` | Tables, named objects with spaces. | Double the quote (`'Bob''s'` → `Bob's`). |
+| `"Net Sales"` | Same as above; cross-shell convenience when single-quote escaping is awkward. | Double the quote (`"He said ""hi"""` → `He said "hi"`). |
+| `[Sales Amount]` | DAX bracket-suffix on a table (`'Sales'[Sales Amount]`) or a lone-bracket model-wide reference (`[Total Sales]`). Object paths only. | Double the closing bracket (`[foo]]bar]` → `foo]bar`). |
+
+Inside quoted segments, `*` is treated as a literal character, not a wildcard. So `'Sa*'` matches a table named exactly `Sa*`.
+
+### DAX-style references (object paths only)
+
+Two DAX-shaped forms are accepted anywhere a `` is allowed:
+
+- **`'Table'[Member]`** - equivalent to `Table/Member`. The bracket-suffix biases ambiguous matches toward columns and measures over hierarchies/partitions.
+- **`[Member]`** - a *lone* measure or column, with no preceding table. Searches the whole model for a measure or column with that name. Measures win when both exist.
+
+```bash
+te get "'Sales'[Amount]" # Same as te get Sales/Amount
+te get "'Net Sales'[Sales Amount]" # Spaced names via DAX form
+te get "[Total Sales]" # Model-wide measure-or-column lookup
+```
+
+### Containers and keywords
+
+Several names act as container keywords. A keyword can stand alone (listing the whole container) or appear inside a path (jumping into that sub-collection on the current parent).
+
+| Keyword | Scope | Meaning |
+| -- | -- | -- |
+| `Tables`, `Measures`, `Columns`, `Hierarchies`, `Partitions` | Model | All objects of that kind across the model. |
+| `Relationships`, `Roles`, `Perspectives`, `Cultures`, `DataSources`, `Expressions`, `CalculationGroups`, `Functions`, `Annotations` | Model | Model-level containers. |
+| `Measures`, `Columns`, `Hierarchies`, `Partitions`, `Calendars`, `CalculationItems` | Table | Sub-containers under a table. |
+| `Levels` | Hierarchy | Levels of a hierarchy. |
+| `Members`, `TablePermissions` (alias `Permissions`) | Role | Children of a role. |
+
+A few examples show how plain and container-scoped paths differ:
+
+```bash
+te get Sales/Revenue # Measure or column on Sales
+te get Sales/Measures/Revenue # Same, container-scoped - disambiguates if other kinds share the name
+te get Sales/Geography/Levels/Year # Specific level of a hierarchy
+te get Roles/Admin/Members/bob@example.com # Role member
+te get Sales/refreshPolicy # Refresh-policy sub-object on a table
+te get "Measures/Revenue/KPI" # KPI sub-object of a measure
+```
+
+Quote a segment to force literal-name matching when a real object name happens to coincide with a keyword. The table literally named `Tables` is `'Tables'`, addressed by `te get "'Tables'"`.
+
+### Wildcards in filter paths
+
+Filter paths add a single wildcard character - `*` - that matches any run of characters within one segment (greedy, single-segment). Wildcards are how `te ls` and similar commands narrow their results.
+
+```bash
+te ls 'Sa*' # Tables whose name starts with Sa
+te ls 'Sales/*Amount' # Children of Sales whose name ends with Amount
+te ls '*/Amount' # An Amount column/measure across every table
+te ls 'Roles/Re*/Members' # Members of every role matching Re*
+```
+
+A filter path with **N segments** produces **N-level-deep** results - wildcards never auto-expand a level beyond what you typed. The single-segment shortcut `te ls Sales` is the exception: an unqualified, non-wildcarded table name expands to the table's direct children to match the "show me what's in Sales" intent. `te ls Sa*`, in contrast, returns just the matching tables - no expansion.
+
+DAX bracket-suffix is rejected in filter paths; quote names containing `[` and `]` if you need to match them literally.
+
+### Errors and hints
+
+Misspelled segments emit a contextual error with a "did you mean" hint when the CLI can guess what you meant. Missing-parent paths fail before the leaf check, so the message points at the segment that's actually wrong. Empty containers (e.g., `te ls Hierarchies` on a model without hierarchies) emit a simply "nothing here" hint rather than an error.
+
+## Global options
+
+These flags are available on every command and can be used before or after the subcommand name.
+
+| Option | Description |
+| -- | -- |
+| `-m, --model ` | Path to semantic model (TMDL folder, `.bim` file, or TE folder). |
+| `-s, --server ` | Workspace name or endpoint (e.g., `MyWorkspace`, `powerbi://...`, `asazure://...`, `localhost`). |
+| `-d, --database ` | Semantic model name on the workspace. |
+| `--local` | Connect to a locally running Power BI Desktop instance (Windows only). |
+| `--auth ` | Auth method: `auto`, `interactive`, `spn`, `env`, `managed-identity` (default: `auto`). |
+| `--output-format ` | Stdout format: `text` (default), `json`, `csv`, `tmsl` (alias `bim`), `tmdl`. `csv` is honored by commands that emit tabular data; `tmsl`/`tmdl` only by `te get` and `te ls` for whole-object serialization. Commands reject formats they don't support. |
+| `--error-format ` | Stderr format for errors, warnings, and hints: `text` (default) or `json`. Other values fall back to text. Independent of `--output-format`, so you can pair JSON stdout with plain-text errors (or vice versa). |
+| `--recent [N]` | Use a recently used model. No value = interactive picker; `N` = Nth most recent (1 = last used). |
+| `--non-interactive` | Disable all interactive prompts. Fail with an actionable error if required input is missing. |
+| `--debug` | Enable debug logging to stderr (connection strings, auth flow, timing). |
+
+For commands that read a model, the resolution order is:
+
+positional `` argument → `--model` global flag → `--server`/`--database` (remote) → active connection from `te connect` → `--recent`.
+
+## Model I/O
+
+### load
+
+Load a semantic model and display a summary of the model — name, compatibility level, and high-level object counts (tables, measures, columns).
+
+```bash
+te load ./model # TMDL folder
+te load model.bim # BIM file
+te load -s MyWorkspace -d MyModel # Remote workspace
+```
+
+### save
+
+Save a model to disk. Use it to write a remote workspace model to local files, convert formats, or persist edits back to the source.
+
+`te save` accepts:
+
+- `-o, --output-path ` - target file or folder. **Optional** - when omitted, `te save` writes back to the source location, preserving the original format.
+- `--serialization ` - `tmdl`, `bim`, `te-folder`, `pbip`, `database.json`. Defaults to inferring from the loaded model (BIM source → BIM, TMDL `SemanticModel/` → TMDL under `definition/`).
+- `--force` - skip validation and overwrite existing output. Some refusals (ambiguous containers, multi-`SemanticModel` project roots) fire even under `--force`.
+- `--skip-bpa` - bypass the BPA gate entirely.
+- `--fix-bpa` - auto-fix BPA violations where rules define a fix expression.
+- `--bpa-rules ` - repeatable; override `bpa.rules` from your CLI config for this single save. Built-in rules still apply unless `bpa.builtInRules` is `false`.
+- `--skip-validation` - skip DAX semantic analysis and validation for fast passthrough downloads.
+- `--supporting-files` - generate Fabric supporting files (`.platform`, `definition.pbism`).
+
+```bash
+te save # Save back to source (no -o needed)
+te save ./model.bim -o ./tmdl-out # Convert BIM to TMDL
+te save -o ./project --serialization pbip # Save as a PBIP project
+te save -o ./out -s my-workspace -d my-model --skip-validation # Fast download
+```
+
+> [!TIP]
+> Use `te save -o -s -d ` to download a remote model to disk. Pair with `--skip-validation` for the fastest passthrough when you only need the bytes (no DAX semantic analysis).
+
+### open
+
+Open a model in Tabular Editor 3 Desktop. **Windows only** (requires TE3 to be installed).
+
+```bash
+te open ./my-model
+```
+
+### init
+
+Create a new empty semantic model at the given path.
+
+```bash
+te init ./new-model
+```
+
+## Model editing
+
+### set
+
+Set a property on a model object. Accepts a ``.
+
+`te set` accepts:
+
+- `-q ` - property name (e.g., `expression`, `formatString`, `description`, `isHidden`).
+- `-i ` - value (use `-` to read from stdin).
+- `--save` / `--save-to ` - persist changes.
+
+```bash
+te set Sales/Amount -q expression -i "SUM(Sales[Amt])" --save
+te set "'Net Sales'[Sales Amount]" -q formatString -i "#,0" --save # DAX form with spaced names
+te set Sales -q isHidden -i true --save
+```
+
+### add
+
+Add an object to the model. Pass a `` for the new object (the parent must already exist; the leaf segment is the new name) and the type via `-t` / `--type`. Relationships keep their shorthand syntax (`Sales[Key]->Dim[Key]`).
+
+`te add` accepts:
+
+- `-t, --type ` - object type. Common values: `Table`, `Measure`, `Column`, `CalculatedColumn`, `Hierarchy`, `Role`, `Perspective`, `Culture`, `CalculationGroup`, `CalculationItem`. Tab-completion is supported; the full list can be retrieved by running `te add --help`.
+- `--if-not-exists` - exit `0` without error if the object already exists. Use this for idempotent CI/CD pipelines.
+
+```bash
+te add Sales/Revenue -t Measure -i "SUM(Sales[Amount])" --save
+te add Sales -t Table --save
+te add "Sales[ProdKey]->Product[ProdKey]" --save # Relationship shorthand
+te add Sales/MarketingFlag -t CalculatedColumn -i "Sales[Amount] > 1000" --if-not-exists --save
+te add Perspectives/Default/Sales --save # Include Sales in the Default perspective
+te add Roles/Reader -t Role --save # New role at the model level
+```
+
+For data-bound tables, `te add` also supports schema detection from SQL, Lakehouse, or Warehouse sources. See `te add --help` for `--source`, `--endpoint`, `--source-table`, `--columns`, etc.
+
+### rm
+
+Remove an object. Checks dependents by default to prevent breaking existing references.
+
+`te rm` accepts:
+
+- `` — positional argument: the object to remove.
+- `--force` — bypass the dependents check.
+- `--if-exists` — exit `0` without error if the object doesn't exist. Use this for idempotent CI/CD pipelines.
+- `--dry-run` — preview the removal without applying it.
+- `--save` — persist the change to the loaded model.
+
+```bash
+te rm Sales/Revenue --save
+te rm "'Sales'[Revenue]" --save # DAX form
+te rm Sales/Revenue --dry-run # Preview only
+te rm Sales/OldMeasure --if-exists --save # Idempotent
+```
+
+### mv
+
+Move or rename a model object. Both source and destination are `` arguments.
+
+```bash
+te mv Sales/Revenue Finance/Revenue --save # Move measure to another table
+te mv Sales/Revenue Sales/TotalRevenue --save # Rename measure
+```
+
+### replace
+
+Find and replace text across model objects. Dry-run by default; add `--save` to apply.
+
+`te replace` accepts:
+
+- `--in ` - scope: `names`, `expressions`, `descriptions`, `displayFolders`, `formatStrings`, `annotations`, `all` (default: `all`).
+- `--regex` - treat the find pattern as a regular expression.
+- `--case-sensitive` - enable case-sensitive matching.
+- `--dry-run` - preview changes without applying. Default behavior.
+- `--save` - persist the mutation to the source location. Mutually exclusive with `--revert` and `--stage`.
+- `--save-to ` - save to a different path (implies `--save`).
+- `--serialization ` - model serialization: `tmdl`, `bim`, `te-folder`.
+- `--force` - save even if the replacement introduces DAX validation errors.
+
+`--in expressions` walks every expression-bearing property:
+
+- **Measure**: `Expression`, `DetailRowsExpression`
+- **KPI**: `TargetExpression`, `StatusExpression`, `TrendExpression`
+- **Partition**: source M, polling M
+- **Table permission**: `FilterExpression`
+- **Calculation group**: selection expressions
+- **Calculated column**: DAX expression
+
+Adding new expression-shaped properties to the model surfaces them automatically.
+
+```bash
+te replace "OldTable" "NewTable" --in expressions --save
+te replace "SUM" "SUMX" --regex --in expressions --save
+```
+
+## Inspection
+
+### ls
+
+List objects with filesystem-like navigation. Takes a `` argument supporting wildcards. Both model-level containers (`Tables`, `Measures`, `Columns`, `Hierarchies`, `Relationships`, `Roles`, `Perspectives`, `Cultures`) and table-scoped containers (`Sales/Measures`, `Sales/Columns`, …) are supported.
+
+`te ls` accepts:
+
+- `--type ` - narrow to one object kind (`table`, `measure`, `column`, `hierarchy`, `partition`, `relationship`, `role`, `perspective`, `culture`). With no `` this is equivalent to typing the matching container keyword.
+- `--paths-only` - emit one object path per line, suitable for piping to `xargs`, `te get`, or `te set`.
+- `--no-multiline` - collapse multi-line cells (typically DAX or M expressions) to a single line and truncate, so rows stay scannable in wide tables. Text output only; JSON/CSV/TMSL output is unaffected.
+- `--output-format tmsl` (alias `bim`) - emit the matching objects as a TMSL/BIM script. Useful for `te ls Tables --output-format bim > tables.json`. `--output-format tmdl` is not supported by `ls` (TMDL is single-object only - use `te get`).
+
+```bash
+te ls # All tables in the model
+te ls Sales # All children of Sales (columns + measures + hierarchies + partitions)
+te ls Sales/Measures # Just Sales's measures
+te ls 'Sales/*Amount' # Children of Sales whose name ends with Amount
+te ls 'Sa*' # Tables whose name starts with Sa (no auto-expansion)
+te ls '*/Amount' # An Amount column/measure across every table
+te ls 'Roles/Re*/Members' # Members of every role matching Re*
+te ls Sales/Geography/Levels # All levels of the Geography hierarchy
+te ls "'Net Sales'/'Sales Amount'" # Quote names containing spaces
+te ls Measures --paths-only # One Table/Measure per line for piping
+te ls --type measure # Same as `te ls Measures`
+te ls Measures --no-multiline # Wide table with column dividers, single-line DAX
+te ls Tables --output-format bim > tables.json # All tables emitted as TMSL/BIM
+```
+
+### get
+
+Get properties of a model object. Takes a ``.
+
+`te get` accepts:
+
+- `-q, --query ` - fetch a single property (e.g. `expression`, `formatString`).
+- `-t, --type ` - disambiguate when the path matches multiple table-children (e.g. a column and a hierarchy with the same name). Values: `Measure`, `Column`, `CalculatedColumn`, `Hierarchy`, `Calendar`, `Partition`, `CalculationItem`.
+- `--output-format tmsl` (alias `bim`) - emit the resolved object as TMSL/BIM JSON.
+- `--output-format tmdl` - emit the resolved object as TMDL (named objects only).
+
+`te get` and `te ls` share a single descriptor catalog, so every property surfaces the same way across formats - the text table, JSON, and CSV all see the same set, and adding a new property to the model exposes it everywhere.
+
+```bash
+te get Sales/Amount -q expression # Print DAX
+te get "'Sales'[Amount]" # DAX form: same as Sales/Amount
+te get "[Total Sales]" # Lone-bracket: model-wide measure-or-column
+te get "'Net Sales'[Sales Amount]" -q expression # DAX form with spaced names
+te get "Sales/Revenue/KPI" # KPI sub-object of a measure
+te get Sales --output-format tmdl # Emit the table as TMDL
+te get Sales --output-format bim # Emit the table as TMSL/BIM
+te get Model -q description
+```
+
+### find
+
+Search for text across model objects.
+
+`te find` accepts:
+
+- `--in ` - as per `te replace` (default `all`).
+- `--regex`, `--case-sensitive`, `--paths-only`.
+- `--no-multiline` - collapse multi-line match context to a single line. Text output only.
+
+`--in expressions` covers every `IExpressionObject` in the model - including KPI `TargetExpression` / `StatusExpression` / `TrendExpression`, measure `DetailRowsExpression`, partition source/polling M, table-permission `FilterExpression`, and calculation-group `MultipleOrEmptySelection` / `NoSelection` expressions - so a literal like `123` set on a KPI's target turns up the same way a measure body would.
+
+```bash
+te find "CALCULATE" --in expressions
+te find "Revenue" --in names
+te find "CALCULATE" --in expressions --paths-only | xargs -I{} te get {} -q expression
+```
+
+### diff
+
+Compare two models for structural differences. Returns the following exit codes: `0` = identical, `1` = differences found, `2` = error.
+
+```bash
+te diff ./model-v1 ./model-v2
+te diff old.bim new.bim
+```
+
+### deps
+
+Analyze an object's upstream and downstream dependencies, or surface unused objects across the model. The single-object form takes a ``.
+
+`te deps` accepts:
+
+- `--unused` - list measures, calculated columns, and **all data columns** that no DAX references and that aren't used in any relationship, hierarchy level, sort-by, variation, AlternateOf base, or calendar time role. Each result shows `(hidden)` in text mode and an `isHidden` field in JSON.
+- `--hidden` - narrow `--unused` to hidden objects only. Hidden, unused objects are the safest prune candidates because nothing user-facing depends on them.
+
+```bash
+te deps Sales/Revenue # Upstream + downstream for one object
+te deps "'Sales'[Revenue]" # DAX form is accepted everywhere a is
+te deps --unused # All unused measures and columns
+te deps --unused --hidden # Only hidden, unused objects
+```
+
+## Analysis and quality
+
+### validate
+
+Validate model expressions, schema integrity, and TOM errors.
+
+`te validate` accepts:
+
+- `--ci ` - emit CI annotations to stderr: `vsts` or `github`.
+- `--trx ` - write results as a VSTEST `.trx` file.
+
+```bash
+te validate ./model
+te validate --ci github --trx results.trx
+```
+
+### bpa run
+
+Run Best Practice Analyzer rules against a model.
+
+`te bpa run` accepts:
+
+- `` - positional argument: path to model (alternative to the `--model` global flag).
+- `-r, --rules ` - path(s) or URL(s) to additional BPA rule file(s) in JSON format. Repeatable.
+- `--no-model-rules` - exclude BPA rules embedded in the model's annotations.
+- `--no-defaults` - exclude built-in default BPA rules.
+- `--vpax ` - load VertiPaq Analyzer stats from a `.vpax` file to enable VPA-aware rules.
+- `--vpa-rules` - include built-in VPA-aware rules (requires `--vpax` or a pre-annotated model).
+- `--allow-external-rules` - allow fetching BPA rule files from URLs embedded in model annotations.
+- `--rule ` - run only specific rule(s) by ID. Repeatable.
+- `--path ` - limit analysis to the tables containing the matched objects. Accepts literal names, container keywords, and wildcards (e.g., `'Sales'`, `'Sa*'`, `'Sales/Measures'`, `'*/Amount'`).
+- `--fix` - apply fix expressions to auto-fix violations where possible.
+- `--save` - save the model back to source after applying fixes.
+- `--save-to ` - save the model to a different path after applying fixes.
+- `--serialization ` - model serialization: `tmdl`, `bim`, `te-folder`.
+- `--fail-on ` - failure threshold: `error` (default) or `warning`. Exits with code `1` when violations meet the threshold.
+- `--ci ` - emit CI logging commands to stderr: `vsts` (Azure DevOps), `github` (GitHub Actions).
+- `--trx ` - write results as a VSTEST `.trx` file to the specified path.
+- `--no-multiline` - collapse multi-line cell content in the violations table to a single line. Text output only.
+
+```bash
+te bpa run --fail-on error --ci github
+te bpa run --fix --save
+te bpa run --rule PERF_UNUSED_HIDDEN_COLUMN
+te bpa run --path Sales # Tables touched by the Sales filter only
+te bpa run --path 'Sa*' # Wildcard - every table starting with Sa
+te bpa run --path Sales/Measures # Path filter applied to the matched tables
+```
+
+### bpa rules
+
+Manage BPA rule collections — list, inspect, initialize, and toggle rules in your local rules file or in model annotations. Built-in rules are read-only - to skip one without losing the rest, use `te bpa rules disable` (do not edit the built-in set directly).
+
+Subcommands:
+
+| Subcommand | Purpose |
+| -- | -- |
+| `add [model]` | Add a new BPA rule. |
+| [`disable`](#bpa-rules-disable) | Disable a built-in BPA rule for the current user. |
+| [`enable`](#bpa-rules-enable) | Re-enable a previously disabled built-in BPA rule. |
+| `ignore [model]` | Add a rule to the model's ignore list. |
+| [`init`](#bpa-rules-init) | Create an empty BPA rules file at the resolved path. |
+| [`list`](#bpa-rules-list) | List BPA rules from all sources with status. |
+| `rm [model]` | Remove a BPA rule. |
+| `set [model]` | Update a BPA rule's properties. |
+| `unignore [model]` | Remove a rule from the model's ignore list. |
+
+All `te bpa rules` subcommands accept:
+
+- `--rules-file ` - path to a BPA rules JSON file. Defaults to the first existing entry of `bpa.rules` in your CLI config (`~/.config/te/config.json`), or the `TE_BPA_RULES` environment variable.
+- `--model-rules` - operate on rules embedded in the model annotation instead of a file.
+
+> [!IMPORTANT]
+> `te bpa rules set` and `te bpa rules rm` refuse to mutate built-in rule IDs. Attempting to do so exits with code `1` and points at `te bpa rules disable`. To customize a built-in rule's behavior, disable the built-in and add a custom copy with a different ID:
+>
+> ```bash
+> te bpa rules disable TE3_BUILT_IN_DATE_TABLE_EXISTS
+> te bpa rules add MY_DATE_TABLE_EXISTS
+> ```
+
+#### bpa rules list
+
+List rules from all sources (built-in, user, model).
+
+`te bpa rules list` accepts:
+
+- (default) Active rules only.
+- `--all` - include disabled and ignored rules.
+- `--disabled` - only built-in rule IDs the user has disabled via `te bpa rules disable`.
+- `--ignored` - only rules whose IDs appear in `BestPracticeAnalyzer_IgnoreRules` on the model.
+- `--no-defaults` - exclude built-in rules from output.
+
+```bash
+te bpa rules list # Active rules
+te bpa rules list --all # Include disabled and ignored rules
+te bpa rules list --ignored
+```
+
+Disabled built-in rules are flagged with a `[disabled]` marker next to the rule ID.
+
+#### bpa rules init
+
+Create an empty BPA rules file (`[]`) at the configured path. Use this once before invoking `te bpa rules set` / `te bpa rules rm` against a path that does not yet exist.
+
+`te bpa rules init` accepts:
+
+- `--force` - overwrite an existing file with `[]`. Required if the target file exists.
+- `--rules-file ` - target file path. Can appear before or after the `init` subcommand.
+
+Path resolution (first match wins): `--rules-file` → `TE_BPA_RULES` env var → first entry of `bpa.rules[]` in your CLI config → `./BPARules.json` (current working directory).
+
+```bash
+te bpa rules init
+te bpa rules init --rules-file ./MyRules.json
+te bpa rules init --force
+```
+
+#### bpa rules disable
+
+Disable an individual built-in BPA rule. The rule ID is added to `bpa.disabledBuiltInRuleIds` in your CLI config. Subsequent gate runs (deploy, save, mutation) and `te bpa run` skip the disabled rule.
+
+The command is idempotent — running `disable` against an already-disabled rule succeeds without modifying the config. It exits with code `1` if `` is not a built-in rule; use `te bpa rules list` to see valid built-in IDs.
+
+```bash
+te bpa rules disable TE3_BUILT_IN_DATE_TABLE_EXISTS
+```
+
+#### bpa rules enable
+
+Re-enable a previously disabled built-in BPA rule by removing the rule ID from `bpa.disabledBuiltInRuleIds`. Exits with code `1` if the rule isn't currently disabled.
+
+```bash
+te bpa rules enable TE3_BUILT_IN_DATE_TABLE_EXISTS
+```
+
+### vertipaq
+
+Analyze VertiPaq storage statistics.
+
+`te vertipaq` accepts:
+
+- `--columns`, `--relationships`, `--partitions`, `--all`.
+- `--export ` - export VertiPaq stats to a `.vpax` file for offline analysis.
+- `--import ` - load a previously exported `.vpax` file and analyze it offline.
+- `--obfuscate` - obfuscate names and expressions in exported VPAX.
+- `--top `, `--stats`, `--annotate`, `--save`.
+
+```bash
+te vertipaq # Columns by size (default)
+te vertipaq --all # Tables, columns, relationships, partitions
+te vertipaq --export stats.vpax
+te vertipaq --import stats.vpax # Analyze offline
+```
+
+### format
+
+Format DAX or M/Power Query expressions.
+
+`te format` accepts:
+
+- `-e, --expression ` - format a single inline expression.
+- `-p, --path ` - format a specific measure/column.
+- `--lang ` - default `dax`.
+- `--save` / `--save-to` - persist formatted expressions.
+
+```bash
+te format --save # Format all DAX
+te format -p Sales/Amount --save # Single measure
+te format -e "SUM ( Sales[Amount] )" # Inline
+te format --lang m --save # Format M
+```
+
+## Execution
+
+### query
+
+Execute a DAX query against a deployed model.
+
+`te query` accepts:
+
+- `-q, --query ` - inline query.
+- `--file ` - query from file.
+- `--limit ` - default 100.
+- `-o, --output-file ` - write results to file (`.csv`, `.tsv`, `.json`, `.dax`).
+- `--trace`, `--cold`, `--plan`, `--runs ` - performance tracing and benchmarking.
+- `--no-validate` - skip pre-execution DAX semantic validation.
+
+```bash
+te query -q "EVALUATE TOPN(5, 'Sales')" -s my-ws -d my-model
+te query --file query.dax --output-format json
+```
+
+### script
+
+Execute one or more C# scripts against a semantic model. The CLI uses the same scripting host as Tabular Editor 3 Desktop, so a script that runs in TE3 runs unchanged here.
+
+`te script` accepts:
+
+- `-S, --script ` - `.cs` / `.csx` file (repeatable).
+- `-e, --expression ` - inline C# (use `-` for stdin).
+- `--save` / `--save-to` / `--serialization`.
+- `--dry-run`, `--timeout `.
+
+```bash
+te script --script fix.cs --save
+te script -e "Info(Model.Tables.Count)"
+echo "Info(Model.Name);" | te script -e -
+```
+
+> [!IMPORTANT]
+> Two behavioral details to know if you're porting an older script:
+>
+> - **No interactive selection in CLI scripts.** The TE3 Desktop helpers `SelectMeasure()`, `SelectTable()`, `SelectColumn()`, `SelectObject()`, and `SelectObjects()` throw `NotSupportedException` when called from `te script` - the CLI has no UI to pop up. Pre-resolve the object(s) outside the script and pass them in, or wrap the call in `try/catch` if the script is shared with TE3.
+> - **Default `using` directives match TE3 Desktop.** Scripts that use `DataTable`, `File`, `StringBuilder`, or `Regex` must include the corresponding `using System.Data;` / `using System.IO;` / `using System.Text;` / `using System.Text.RegularExpressions;` directive explicitly.
+
+> [!NOTE]
+> **Preprocessor symbols for cross-host scripts.** Scripts compiled by `te script` have the symbol `TECLI` defined. TE3 Desktop scripts have `TE3` defined instead, plus version-bracketed symbols like `TE3_3_10_OR_GREATER` ... `TE3_3_X_OR_GREATER` for the current TE3 minor version. TE2 defines neither symbol. Use these to write portable scripts:
+>
+> ```csharp
+> #if TECLI
+> // CLI-only code - no UI calls
+> Info($"Running under the CLI on {Environment.OSVersion.Platform}");
+> #elif TE3
+> // TE3 Desktop-only code - UI APIs available
+> ShowMessage("Hello from TE3");
+> #else
+> // TE2 (legacy) - neither TECLI nor TE3 is defined
+> Info("Hello from TE2");
+> #endif
+>
+> #if TE3_3_15_OR_GREATER
+> // Gated on a specific TE3 minor version
+> #endif
+> ```
+>
+> See @csharp-scripts for the broader cross-version scripting story.
+
+### macro
+
+Manage and run macros from a macros JSON file (typically `MacroActions.json`). The macros file is resolved in this order: `--macros ` → `TE_MACROS_PATH` env var → `macros` in CLI config → `./MacroActions.json`.
+
+Subcommands:
+
+| Subcommand | Purpose |
+| -- | -- |
+| `list` | List macros. |
+| [`run `](#macro-run) | Run a macro. |
+| `add ` | Add a macro. |
+| `set ` | Update macro properties. |
+| `rm ` | Remove a macro. |
+| `sort` | Sort and re-assign IDs. |
+| [`init`](#macro-init) | Create an empty macros file at the resolved path. |
+
+#### macro init
+
+Create an empty macros file (`{"Actions":[]}`) at the configured path. Use this once when the resolved macros file does not yet exist.
+
+`te macro init` accepts:
+
+- `--force` - overwrite an existing file. Required if the target exists.
+- `--macros ` - target file path. Can appear before or after the `init` subcommand.
+
+```bash
+te macro init
+te macro init --macros ./project-macros.json
+te macro init --force
+```
+
+#### macro run
+
+Run a macro. Macros that emit tables via `dataTable.Output()` render formatted output in the terminal, so DAX-style query macros work the same in `te macro run` as they do in TE3.
+
+`te macro run` accepts:
+
+- `--on ` - set the macro's selection context to a single named object (a table, measure, column, …). Equivalent to right-clicking that object in TE3 and invoking the macro from the context menu.
+- `--save` / `--save-to` - persist any changes the macro makes.
+
+```bash
+te macro run "Hide all measures"
+te macro run "Format DAX" --on Sales/Revenue --save
+te macro run "Format DAX" --on "'Net Sales'[Sales Amount]" --save # DAX form works in --on too
+```
+
+## Deployment and refresh
+
+### deploy
+
+Deploy a semantic model to Power BI, Fabric, or Azure Analysis Services.
+
+`te deploy` accepts:
+
+- `-s, --server` / `-d, --database` - target workspace and model.
+- `--deploy-full` - overwrite + connections + partitions + shared expressions + roles + role members.
+- `--deploy-connections`
+- `--deploy-partitions`
+- `--skip-refresh-policy`
+- `--deploy-roles`
+- `--deploy-role-members`
+- `--deploy-shared-expressions`
+- `--create-only`
+- `--xmla ` - generate XMLA/TMSL script instead of deploying (`-` for stdout).
+- `--skip-bpa` - bypass the BPA gate entirely.
+- `--fix-bpa` - auto-fix BPA violations where rules define a fix expression.
+- `--bpa-rules ` - repeatable; override `bpa.rules` from your CLI config for this single deploy. Built-in rules still apply unless `bpa.builtInRules` is `false`.
+- `--force` - skip interactive confirmation (required for CI).
+- `--ci ` - `vsts` or `github`.
+- `--profile ` - one-shot use of a saved @te-cli-auth profile.
+
+```bash
+te deploy ./model -s my-workspace -d my-model --force --ci github
+te deploy ./model --xmla script.tmsl # Generate TMSL only
+te deploy ./model --profile staging --force
+```
+
+> [!IMPORTANT]
+> `te deploy` runs the Best Practice Analyzer as a gate before executing. In interactive mode, a summary + confirmation prompt is shown with **`n` as the safe default**. In CI, pass `--force` to skip the prompt. See @te-cli-config for BPA gate configuration.
+
+### refresh
+
+Trigger a data refresh on a deployed model.
+
+`te refresh` accepts:
+
+- `--type ` - `full`, `dataonly`, `automatic`, `calculate`, `clearvalues`, `defragment`, `add` (default: `automatic`).
+- `--table ` - refresh specific table(s); repeatable.
+- `--partition ` - refresh specific partition(s).
+- `--apply-refresh-policy` - apply the incremental refresh policy to determine which partitions are refreshed.
+- `--effective-date ` - set the effective date used by the refresh policy.
+- `--max-parallelism ` - set the maximum number of partitions to refresh in parallel.
+- `--dry-run` - output the TMSL script without executing.
+- `--no-progress`, `--trace [path]`.
+
+```bash
+te refresh --type full # Full refresh
+te refresh --table Sales --type full # Single table
+te refresh --type full --dry-run > refresh.tmsl # Emit TMSL only
+```
+
+### incremental-refresh
+
+Manage incremental refresh policies on tables.
+
+```bash
+te incremental-refresh show
+```
+
+Additional subcommands (`set`, `remove`, `apply`) are documented via `te incremental-refresh --help`.
+
+## Testing
+
+### test run
+
+Run a suite of DAX assertion tests against a deployed model.
+
+`te test run` accepts:
+
+- `--suite ` - test-suite directory (default: `.te-tests/`).
+- `--tag ` - only tests with this tag.
+- `--fail-on ` - `error` (default) or `warning`.
+- `--ci `, `--trx ` - CI annotations and TRX output.
+
+```bash
+te test run --ci github --trx results.trx
+te test run --tag revenue
+```
+
+### test init / spec / use / list / snapshot / compare
+
+Additional subcommands scaffold tests, print the assertion spec format, switch the active suite, list suites, capture snapshots, and compare models. See `te test --help` for details.
+
+```bash
+te test init --example # Scaffold an example suite
+te test spec # Print the full assertion format reference
+te test init --from-model --model ./my-model # Generate stubs from your measures
+```
+
+## Connection and authentication
+
+### connect
+
+Set (or display) the active connection for the current terminal session. See @te-cli-auth.
+
+```bash
+te connect # Show current active connection
+te connect my-workspace my-model # Remote
+te connect ./model # Local
+te connect --local # Power BI Desktop (Windows)
+te connect --profile prod # Activate a saved profile
+te connect --clear # Clear the active connection (and any workspace mirror)
+```
+
+#### Workspace mode (`-w` / `--workspace`)
+
+Pair a primary source with a secondary target so every subsequent `--save` mirrors the model between the two. Useful for keeping a local working copy of a remote workspace, or pushing local edits to a workspace as you save.
+
+- `te connect -w ./src` - primary is remote; `./src` receives an initial TMDL export and mirrors every save.
+- `te connect ./src -w ` - primary is local; an initial deploy pushes the model to the workspace, and subsequent saves re-deploy automatically.
+- `--workspace-format ` - choose the on-disk format when mirroring to a folder/file (e.g., `-w ./model.bim` infers BIM).
+- `--force` - required when the target already exists (non-empty folder, existing database). Without it, `te connect` shows an interactive `y/n` prompt with `n` as the safe default.
+
+Once active, `te set --save`, `te rm --save`, `te script --save`, etc. all dual-save transparently. Save order is always **local first, then remote** so the on-disk copy reflects the latest user change even if the server push fails. Clear the mirror with `te connect --clear`.
+
+```bash
+te connect Finance "Revenue Model" -w ./revenue-model # Mirror remote → local TMDL
+te connect ./revenue-model -w Finance "Revenue Model" # Mirror local → remote
+```
+
+### auth login / status / logout
+
+Manage cached authentication. See @te-cli-auth.
+
+### profile list / show / set / remove
+
+Manage named connection profiles. See @te-cli-auth.
+
+## Configuration
+
+### config show / paths / init / set
+
+View and manage CLI configuration and TE3 path overrides. See @te-cli-config.
+
+```bash
+te config show # Display all settings
+te config paths # Resolved TE3 file paths
+te config init # Create default config
+te config set autoFormat true
+```
+
+### license
+
+`te license` is reserved for the GA release and is not available in this preview build. The command is still wired up to the parser - so existing scripts that invoke it won't blow up at parse time - but every subcommand exits with status `1` and a "not available in this preview build" message. See the [Preview notice](xref:te-cli#preview-notice) on the overview page for the broader licensing outlook.
+
+### migrate
+
+Reference guide showing how legacy Tabular Editor 2 CLI flags map to the new CLI. Useful as a live lookup while porting a TE2-based pipeline. See @te-cli-migrate for the full migration guide.
+
+```bash
+te migrate # Full flag mapping table
+te migrate -A # Look up a single TE2 flag
+te migrate --output-format json # Machine-readable mapping
+```
+
+## Shell
+
+### interactive
+
+Start a guided REPL session with a model-aware prompt. See @te-cli-interactive.
+
+```bash
+te interactive # Connect later
+te interactive ./model # Start with a local model
+te interactive -s MyWorkspace -d MyModel # Start with a remote model
+```
+
+Quoting and DAX-style references work the same as outside the session - see the [Object paths](#object-paths) section above and @te-cli-interactive for details on bracket-aware argv splitting inside the REPL.
+
+### completion
+
+Generate a shell completion script. See @te-cli-install.
+
+```bash
+te completion bash
+te completion zsh
+te completion pwsh
+```
+
+## Exit codes
+
+| Exit | Meaning |
+| -- | -- |
+| `0` | Success. |
+| `1` | Generic failure (invalid arguments, command failed, validation errors, auth failure, BPA gate failed at severity ≥ error). |
+| `2` | Non-zero diff (`te diff`) - models differ. |
+
+For fine-grained control in CI pipelines, combine exit codes with `--ci ` annotations and `--trx` results files - see @te-cli-cicd.
+
+## Related pages
+
+- @te-cli - overview and framing.
+- @te-cli-install - install and set up the CLI.
+- @te-cli-auth - authenticate and manage connections.
+- @te-cli-config - configuration file, BPA gate, post-mutation behavior.
+- @te-cli-migrate - TE2 → TE3 flag mapping.
diff --git a/content/features/te-cli/te-cli-config.md b/content/features/te-cli/te-cli-config.md
new file mode 100644
index 000000000..95c442a49
--- /dev/null
+++ b/content/features/te-cli/te-cli-config.md
@@ -0,0 +1,257 @@
+---
+uid: te-cli-config
+title: Custom Configuration
+author: Peer Grønnerup
+updated: 2026-04-20
+applies_to:
+ products:
+ - product: Tabular Editor 2
+ none: true
+ - product: Tabular Editor 3
+ none: true
+ - product: Tabular Editor CLI
+ full: true
+---
+# Custom Configuration
+
+[!INCLUDE [te-cli-preview-notice](includes/te-cli-preview-notice.md)]
+
+The Tabular Editor CLI reads optional configuration from a JSON file. Configuration controls three things:
+
+- **File paths** — where the CLI reads macros, BPA rules, and (optionally) the TE3 Desktop executable, and where to write the query log.
+- **Behavioral defaults** — BPA gates, auto-format, validation.
+- **Saved connection profiles** — the list of named profiles you can switch between.
+
+The CLI is self-contained - it does not read from or write to any Tabular Editor 3 desktop install path. BPA rules and macros files must be set explicitly via this config (or initialized on demand with `te bpa rules init` / `te macro init`).
+
+Most users don't need to edit the config file directly - `te config show`, `te config set `, and `te profile set` cover the common operations.
+
+## Config file location
+
+The following locations are checked in this order:
+
+1. `$TE_CONFIG` environment variable (if set and the file exists).
+2. `~/.config/te/config.json` (on Windows, `%USERPROFILE%\.config\te\config.json`).
+3. No config file - the CLI uses built-in defaults.
+
+`TE_CONFIG` is honored consistently by every config-file operation - `te config show`, `te config set`, `te config init`, and `te config paths` all read and write at the resolved path. This is primarily intended for testing, scripted installs, and per-environment configuration.
+
+To create a default config:
+
+```bash
+te config init # Create config at TE_CONFIG (or ~/.config/te/config.json)
+te config init --force # Overwrite existing config
+```
+
+## Viewing configuration
+
+```bash
+te config show # Display all settings
+te config show --output-format json # Machine-readable
+te config paths # Show resolved macros and BPA rule paths
+```
+
+Use `te config paths` to see which files the CLI will actually use for macros and BPA rules. It's handy when debugging missing data files. The output shows two rows: `macros` (the resolved macros file path or `[not set]`) and `bpa.rules` (the first existing BPA rules file resolved by the path resolver, or `[not set]`).
+
+> [!NOTE]
+> `te config paths` emits `null` fields explicitly in `--output-format json` mode (e.g., `{"macros": null, "bpa": {"rules": null}}`). Reporting resolution outcomes is the command's whole purpose, so `null` is a meaningful "tried but resolved to nothing" answer. `te config show --output-format json` strips null fields by default, so consumers should parse it tolerantly.
+
+## Setting values
+
+```bash
+te config set autoFormat true
+te config set bpa.onDeploy false
+te config set hidePreviewNotice true
+te config set macros null # Clear a path override
+```
+
+Unknown keys fail with exit code `1` and an error that lists the valid keys.
+
+If no config file exists, `te config set` auto-creates one at the resolved path (`$TE_CONFIG` if set, otherwise `~/.config/te/config.json`) before applying the change.
+
+> [!NOTE]
+> Every key in the schema is settable via `te config set`, including nested keys via dotted paths (`bpa.onDeploy`, `formatOptions.useSqlBiDaxFormatter`, etc.). The only exception is `formatVersion`, which the CLI manages automatically. Run `te config paths` to find the config file if you'd rather edit the JSON directly.
+
+## Full schema
+
+The complete JSON config schema with all keys at their default values. Use this as a reference when editing the config file directly, or when looking up the dotted path for a `te config set` call.
+
+```json
+{
+ "formatVersion": 1,
+ "macros": null,
+ "autoFormat": false,
+ "validateOnMutation": true,
+ "vertipaqOnRefresh": false,
+
+ "bpa": {
+ "rules": null,
+ "onDeploy": true,
+ "onSave": true,
+ "onMutation": false,
+ "builtInRules": true,
+ "disabledBuiltInRuleIds": null
+ },
+
+ "interactiveEditMode": "stage",
+
+ "formatOptions": {
+ "useSemicolons": false,
+ "shortFormat": false,
+ "skipSpaceAfterFunction": false,
+ "useSqlBiDaxFormatter": false
+ },
+
+ "hidePreviewNotice": false,
+ "spinner": true,
+ "debug": false,
+ "disableTelemetry": false,
+
+ "queryLog": null,
+ "te3ExePath": null,
+
+ "profiles": {}
+}
+```
+
+### File paths
+
+Set these in your config to avoid passing the same paths on every command. Per-command flags and environment variables override config values; see [Path resolution priority](#path-resolution-priority) below.
+
+| Key | Meaning |
+| -- | -- |
+| `macros` | Explicit path to a macros JSON file (typically `MacroActions.json`). Resolved by every `te macro` command. Point at a shared file (network share, repo-local, or even the TE3 desktop file) to reuse the same set of macros across machines and between the CLI and TE3 Desktop. |
+| `bpa.rules` | Ordered list of paths or URLs to BPA rule files. The deploy/save gate loads **every** existing entry; `te bpa rules list` and `te config paths` use the first existing entry. Comma-separated values on `te config set bpa.rules ...` are split into the array. |
+| `te3ExePath` | Explicit path to the Tabular Editor 3 Desktop executable (`TabularEditor.exe`). Used **only** by `te open` to launch the desktop app; safe to leave unset on Linux/macOS or when you don't use `te open`. If unset, `te open` falls back to a `PATH` lookup. |
+| `queryLog` | Path to a log file where every `te query` invocation appends its query text and execution metadata. Useful for audit trails or analyzing query patterns over time. Supports `~` for the home directory (e.g., `~/.config/te/queries.log`). |
+
+### Path resolution priority
+
+For each user-provided file (macros, BPA rules), the CLI resolves the path in this order:
+
+1. **Command-line flag** - `--macros ` for macro commands; `--bpa-rules ` for the deploy/save gate; `--rules-file ` for `te bpa rules` subcommands.
+2. **Environment variable** - `TE_MACROS_PATH` for macros, `TE_BPA_RULES` for BPA rules.
+3. **CLI config** - `macros` for macros, the first existing entry of `bpa.rules[]` for BPA rules.
+
+The CLI does not auto-detect any TE3 install location - configure these explicitly. To start from a default file in the current working directory, run `te macro init` (creates `./MacroActions.json`) or `te bpa rules init` (creates `./BPARules.json`).
+
+Run `te config paths` to see which file the CLI actually resolved.
+
+### Behavioral defaults
+
+All BPA-related settings live under the `bpa` object and are addressed via dotted keys on `te config set`.
+
+| Key | Default | Description |
+| -- | -- | -- |
+| `autoFormat` | `false` | Run the DAX Formatter on modified expressions after `te add` / `te set` / `te mv` / `te macro run`. Uses the in-house formatter by default; opt into the SQL BI web service via `formatOptions.useSqlBiDaxFormatter`. |
+| `validateOnMutation` | `true` | After a mutating command (`add`, `set`, `mv`, `replace --save`, `macro run`), check that every `Table[Column]` reference in the model still resolves. Catches dangling references introduced by renames or removals before they reach deploy. |
+| `bpa.onMutation` | `false` | Run a scoped BPA analysis after each mutating command (`set`, `add`, `mv`, `rm`, `macro run`). Only the affected table's objects are checked, not the whole model - useful for fast feedback during iterative edits. |
+| `bpa.onDeploy` | `true` | Run the BPA gate before `te deploy` executes. The deploy is aborted if any rule fires at severity ≥ error. Bypass per-invocation with `--skip-bpa`, or auto-fix with `--fix-bpa`. |
+| `bpa.onSave` | `true` | Run the BPA gate before `te save -o` writes to disk. Bypass per-invocation with `--skip-bpa` or `--force`. |
+| `bpa.builtInRules` | `true` | Include the curated built-in BPA rule set whenever the gate runs. Set to `false` to ignore built-ins entirely; the gate then runs only the rules configured via `bpa.rules` and any model-embedded rules. |
+| `bpa.disabledBuiltInRuleIds` | `null` | IDs of individual built-in rules to exclude from the gate. Mutated by `te bpa rules disable ` / `te bpa rules enable ` - prefer those over editing the array directly. |
+| `vertipaqOnRefresh` | `false` | After a successful refresh (`full`, `dataonly`, `automatic`, or `add`), automatically run VertiPaq analysis to show storage stats for the refreshed tables. Useful for catching unexpected cardinality or memory regressions immediately. |
+| `interactiveEditMode` | `stage` | Default behavior for in-memory mutations inside `te interactive`. `stage` keeps mutations in memory until `save` is invoked (safest); `save` writes to source after every mutating command (use with care on remote sources - every `set` triggers an XMLA write); `revert` discards mutations after each command unless `--save` or `--stage` was passed. Per-command `--save` / `--revert` / `--stage` flags always override. |
+| `disableTelemetry` | `false` | Opt out of anonymous usage telemetry. The CLI collects coarse-grained command usage data (command name, exit code, duration) to inform feature priority. The CLI never collects model content, paths, or query text. |
+
+```bash
+te config set bpa.rules "/etc/te/team.json,/etc/te/strict.json"
+te config set bpa.onDeploy true
+te config set bpa.builtInRules false
+te config set bpa.disabledBuiltInRuleIds "TE3_BUILT_IN_DATE_TABLE_EXISTS,TE3_BUILT_IN_HIDE_FOREIGN_KEYS"
+```
+
+### Format options
+
+Applied whenever the CLI invokes a DAX formatter (for `te format` and, when enabled, `autoFormat` on mutations). The CLI ships with an in-house formatter that works fully offline; opt into the SQL BI [daxformatter.com](https://www.daxformatter.com) web service via `formatOptions.useSqlBiDaxFormatter` if you need that style or want to match the behavior of TE2 or TE3 with "Use daxformatter.com..." enabled.
+
+| Key | Default | Description |
+| -- | -- | -- |
+| `formatOptions.useSemicolons` | `false` | Use `;` as the list separator (European/EU locale convention). The default `,` matches the en-US locale. |
+| `formatOptions.shortFormat` | `false` | Prefer short, single-line formatting where possible instead of the default multi-line layout. |
+| `formatOptions.skipSpaceAfterFunction` | `false` | Omit the space between a function name and its opening parenthesis (e.g. `SUM(x)` instead of `SUM (x)`). |
+| `formatOptions.useSqlBiDaxFormatter` | `false` | Format DAX via the [SQL BI daxformatter.com](https://www.daxformatter.com) web service instead of the in-house formatter. Requires internet access. The in-house formatter (default) works offline and matches the Tabular Editor 3 Desktop default. |
+
+### Display
+
+Settings that control the CLI's terminal output and diagnostic verbosity.
+
+| Key | Default | Description |
+| -- | -- | -- |
+| `hidePreviewNotice` | `false` | Suppress the yellow preview banner. **Ignored within 14 days of expiry.** |
+| `spinner` | `true` | Show animated progress indicators in the terminal. Disable for CI. |
+| `debug` | `false` | Always enable debug logging (same as passing `--debug`). |
+
+### Profiles
+
+Saved connection profiles live under the `profiles` key. Don't edit them by hand - use `te profile set / remove / list`. See @te-cli-auth for profile management.
+
+Profiles can carry **overrides** that override the behavioral defaults above whenever the profile is active. This is how a dev profile can relax validation and BPA while a prod profile keeps them strict:
+
+```bash
+te profile set dev --validate-on-mutation false --bpa-on-deploy false
+te profile set prod --auto-format true
+```
+
+## BPA gate
+
+The BPA gate is the safety net that prevents a model with rule violations from being saved or deployed. It runs automatically when the following commands are executed:
+
+- `te deploy` runs the gate unless `--skip-bpa` is passed or `bpa.onDeploy` is `false`.
+- `te save` runs the gate unless `--skip-bpa` (or `--force`) is passed or `bpa.onSave` is `false`.
+- `te add`, `te set`, `te mv`, `te macro run` run the gate only when `bpa.onMutation` is `true`.
+
+The gate loads BPA rules from `bpa.rules` and, by default, the built-in rule set (controlled by `bpa.builtInRules`). Built-in rules can be individually excluded via `bpa.disabledBuiltInRuleIds` - managed with `te bpa rules disable ` / `te bpa rules enable `.
+
+When the gate fires and finds violations at severity ≥ `error`, the command fails with exit code `1` and a summary of the violations. Options to resolve:
+
+- `--fix-bpa` - apply the rule's `fixExpression` in memory for the deploy/save artifact; source files are not modified.
+- `--skip-bpa` - disable the gate for this one command.
+- `--bpa-rules ` - repeatable; override `bpa.rules` for this single `te deploy` or `te save` invocation. Built-in rules still apply unless `bpa.builtInRules` is `false`.
+
+Run `te bpa run` independently to preview the gate's behavior without deploying:
+
+```bash
+te bpa run ./model --fail-on error
+te bpa run ./model --fix --save # Apply fixes to the source
+```
+
+### Built-in BPA rules
+
+The CLI ships a single canonical set of built-in BPA rules embedded as a JSON resource. Built-in rules are read-only - `te bpa rules set` and `te bpa rules rm` refuse to mutate built-in IDs and point users at `te bpa rules disable` instead. To customize a built-in rule's behavior, copy it into your local rules file as a new rule with a different ID and disable the built-in.
+
+Both `bpa.builtInRules` and `bpa.disabledBuiltInRuleIds` apply consistently to the deploy/save/mutation gate **and** the manual `te bpa run` command - disabling a rule once via `te bpa rules disable` excludes it everywhere.
+
+## Post-mutation behavior
+
+When you run a mutating command (`te add`, `te set`, `te mv`, `te replace --save`, `te macro run`), the CLI performs these checks automatically:
+
+1. **TOM errors** are always surfaced. Invalid DAX or M in measures, columns, partitions, or calculation items always fails the command.
+2. **Schema validation** (`validateOnMutation`, default `true`) verifies that `Table[Column]` references in DAX still resolve, cross-checking metadata consistency.
+3. **DAX auto-format** (`autoFormat`, default `false`) formats any expressions touched by the mutation via the built-in DAX Formatter when enabled.
+4. **BPA on mutation** (`bpa.onMutation`, default `false`) runs BPA after the mutation when enabled, warning or failing based on `--fail-on`.
+
+Disable a check with `te config set false`, or scope the relaxation to a specific environment via a profile.
+
+## Environment variables
+
+Use the following CLI-specific environment variables for paths, behavior, and diagnostics. For Azure authentication variables (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_CERTIFICATE_PATH`, etc.), see @te-cli-auth.
+
+| Variable | Purpose |
+| -- | -- |
+| `TE_CONFIG` | Path to an alternative config file. Honored by every `te config` operation (`show`, `set`, `init`, `paths`). |
+| `TE_MACROS_PATH` | Override the macros file path (second in resolution order - see above). Read by `te macro` commands. |
+| `TE_BPA_RULES` | Override the BPA rules file/URL list used by `te bpa run` and `te bpa rules` subcommands. |
+| `TE_BPA_CONFIG` | Override the path to the BPA gate config (`.te-bpa.json`) the deploy/save gate reads. |
+| `TE3_EXE_PATH` | Path to the Tabular Editor 3 desktop binary. Used **only** by `te open`; safe to leave unset on Linux/macOS or when you don't use `te open`. Falls back to `PATH` lookup. |
+| `TE_DEBUG` | Set to `1` to enable debug logging globally (same as `--debug` or `debug: true` in config). |
+| `NO_SPINNER` | Set to `1` or `true` to disable animated progress indicators (alternative to `spinner: false` in config). |
+| `CI` | Auto-detected. When `1` or `true`, the CLI disables the spinner and switches to plain output. Most CI runners set this automatically. |
+| `TE_SESSION` | Override the per-terminal session ID used for active-connection state. Useful for running multiple isolated CLI sessions inside the same shell, e.g. in parallel CI matrix jobs. |
+| `TE_COMPAT` | Set to `te2` to force TE2-compatibility mode - see @te-cli-migrate. |
+
+## Related pages
+
+- @te-cli-auth - profiles, authentication, and credential storage.
+- @te-cli-commands - `te config` subcommands.
+- @te-cli-cicd - configuring the BPA gate for pipelines.
diff --git a/content/features/te-cli/te-cli-install.md b/content/features/te-cli/te-cli-install.md
new file mode 100644
index 000000000..4c6ed3c4a
--- /dev/null
+++ b/content/features/te-cli/te-cli-install.md
@@ -0,0 +1,200 @@
+---
+uid: te-cli-install
+title: Installation and Setup
+author: Peer Grønnerup
+updated: 2026-05-06
+applies_to:
+ products:
+ - product: Tabular Editor 2
+ none: true
+ - product: Tabular Editor 3
+ none: true
+ - product: Tabular Editor CLI
+ full: true
+---
+# Installation and Setup
+
+[!INCLUDE [te-cli-preview-notice](includes/te-cli-preview-notice.md)]
+
+The Tabular Editor CLI ships as a single self-contained executable named `te` (`te.exe` on Windows). It has no external runtime dependencies.
+
+## Download
+
+1. Sign in at [tabulareditor.com](https://tabulareditor.com/download-tabular-editor-cli) with a Tabular Editor account.
+2. Download the archive for your platform and architecture:
+
+ | Platform | 64-bit (Intel/AMD) | ARM64 | Archive |
+ | -- | -- | -- | -- |
+ | Windows | `te-win-x64.zip` | `te-win-arm64.zip` | `.zip` |
+ | macOS | `te-osx-x64.tar.gz` (Intel) | `te-osx-arm64.tar.gz` (Apple Silicon) | `.tar.gz` |
+ | Linux | `te-linux-x64.tar.gz` | `te-linux-arm64.tar.gz` | `.tar.gz` |
+
+ Pick the ARM64 build on Apple Silicon Macs (M1 and newer), Windows on ARM devices, and ARM-based Linux servers (including AWS Graviton, Azure Ampere, and Raspberry Pi 64-bit). Pick the `x64` build on everything else.
+
+## Install
+
+Unzip the archive into a folder of your choice and add that folder to `PATH` so you can invoke `te` from any working directory.
+
+### Windows (PowerShell)
+
+#### x64
+
+```powershell
+Expand-Archive te-win-x64.zip -DestinationPath "$env:LOCALAPPDATA\Programs\te"
+[Environment]::SetEnvironmentVariable(
+ "PATH",
+ [Environment]::GetEnvironmentVariable("PATH", "User") + ";$env:LOCALAPPDATA\Programs\te",
+ "User")
+```
+
+#### ARM64
+
+```powershell
+Expand-Archive te-win-arm64.zip -DestinationPath "$env:LOCALAPPDATA\Programs\te"
+[Environment]::SetEnvironmentVariable(
+ "PATH",
+ [Environment]::GetEnvironmentVariable("PATH", "User") + ";$env:LOCALAPPDATA\Programs\te",
+ "User")
+```
+
+### macOS
+
+#### Apple Silicon (ARM64)
+
+```bash
+mkdir -p ~/.local/bin
+tar -xzf te-osx-arm64.tar.gz -C ~/.local/bin
+chmod +x ~/.local/bin/te
+echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc # or ~/.bashrc
+```
+
+#### Intel (x64)
+
+```bash
+mkdir -p ~/.local/bin
+tar -xzf te-osx-x64.tar.gz -C ~/.local/bin
+chmod +x ~/.local/bin/te
+echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc # or ~/.bashrc
+```
+
+On macOS, the binary is signed with our Apple Developer ID and notarized by Apple, so the first run completes without a "cannot verify developer" Gatekeeper warning. Network access on first run is recommended so Gatekeeper can fetch the notarization ticket; offline first-runs may briefly prompt before being unblocked once network returns.
+
+### Linux
+
+#### x64
+
+```bash
+mkdir -p ~/.local/bin
+tar -xzf te-linux-x64.tar.gz -C ~/.local/bin
+chmod +x ~/.local/bin/te
+echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc
+```
+
+#### ARM64
+
+```bash
+mkdir -p ~/.local/bin
+tar -xzf te-linux-arm64.tar.gz -C ~/.local/bin
+chmod +x ~/.local/bin/te
+echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc
+```
+
+> [!NOTE]
+> The PATH change takes effect in **new** shell sessions. To run `te` in the shell where you ran the install, open a new terminal, or reload your profile: `source ~/.bashrc` / `source ~/.zshrc` on macOS/Linux, or close and reopen PowerShell on Windows.
+
+## Verify
+
+Check the installed version and list available commands:
+
+```bash
+te --version
+te --help
+```
+
+`te --help` prints a colorized help index grouping commands by family. Every subcommand accepts `--help` for detailed usage:
+
+```bash
+te deploy --help
+te bpa run --help
+```
+
+## Hide the preview banner
+
+The CLI prints a yellow preview banner on stderr by default. To suppress it run:
+
+```bash
+te config set hidePreviewNotice true
+```
+
+> [!WARNING]
+> The banner reappears on every command within **14 days of the preview end date** (2026-09-30), regardless of `hidePreviewNotice`. This ensures you have visible warning before the CLI stops functioning.
+
+## Shell completion
+
+The CLI provides tab-completion scripts for **Bash**, **Zsh**, and **PowerShell**. Pick the block that matches your shell — each one installs the completion persistently for new shell sessions.
+
+### Bash (macOS/Linux)
+
+```bash
+mkdir -p ~/.local/share/bash-completion/completions
+te completion bash > ~/.local/share/bash-completion/completions/te
+```
+
+### Zsh (macOS/Linux)
+
+```zsh
+mkdir -p ~/.zfunc
+te completion zsh > ~/.zfunc/_te
+echo 'fpath=(~/.zfunc $fpath); autoload -U compinit; compinit' >> ~/.zshrc
+```
+
+### PowerShell (Windows/macOS/Linux)
+
+```powershell
+Add-Content $PROFILE 'te completion pwsh | Out-String | Invoke-Expression'
+```
+
+Open a new shell session for completion to take effect.
+
+Completion covers subcommands, global flags, and model paths (where tab-completion against the filesystem is meaningful).
+
+## Cross-platform feature matrix
+
+Most features are identical across platforms. A handful depend on Windows-only transports:
+
+| Feature | Windows | macOS / Linux |
+| -- | -- | -- |
+| Load/save BIM and TMDL | Yes | Yes |
+| Deploy to Power BI / Fabric / Azure Analysis Services | Yes | Yes |
+| Best Practice Analyzer and VertiPaq Analyzer | Yes | Yes |
+| C# scripting | Yes | Yes |
+| DAX queries against cloud models | Yes | Yes |
+| Authentication: browser, device-code, service principal, env, managed identity | Yes | Yes |
+| Connect to local SSAS instance (TCP transport) | Yes | **No** |
+| Connect to Power BI Desktop (named-pipe transport) | Yes | **No** |
+
+> [!IMPORTANT]
+> Local SSAS and Power BI Desktop connections rely on Windows-only transport protocols. All cloud-based workflows (Power BI Service, Fabric, Azure Analysis Services) work on every platform.
+
+## Updating
+
+To update to a newer preview build, download the latest archive and overwrite the previous installation. Configuration and cached credentials are stored outside the install folder (see and ) and are preserved across updates.
+
+## Uninstalling
+
+1. Delete the install folder.
+2. Remove the PATH entry.
+3. (Optional) Clear cached credentials and config:
+ - Run `te auth logout` first - it removes all cached tokens and SPN records from the active backend (OS keystore or file fallback).
+ - Delete `~/.config/te/` (config and saved profiles).
+ - Delete `~/.te-cli/` (residual cache files; only present when the file fallback was in use, or as legacy from older CLI builds).
+ - To also purge the OS-native keystore entries - usually unnecessary, since `te auth logout` already clears them - see:
+ - **Windows:** Credential Manager → Windows Credentials → entries named `com.tabulareditor.cli...` or `te-cli`.
+ - **Linux:** `secret-tool search Component te-cli` and `secret-tool clear ...`, or use seahorse.
+ - **macOS:** Keychain Access → search for `com.tabulareditor.cli`.
+
+## Next steps
+
+- @te-cli-auth - authenticate to Power BI, Fabric, or Azure Analysis Services.
+- @te-cli-commands - full command reference.
+- @te-cli-interactive - guided REPL mode.
diff --git a/content/features/te-cli/te-cli-interactive.md b/content/features/te-cli/te-cli-interactive.md
new file mode 100644
index 000000000..2b937c3f1
--- /dev/null
+++ b/content/features/te-cli/te-cli-interactive.md
@@ -0,0 +1,98 @@
+---
+uid: te-cli-interactive
+title: Interactive Mode
+author: Peer Grønnerup
+updated: 2026-05-12
+applies_to:
+ products:
+ - product: Tabular Editor 2
+ none: true
+ - product: Tabular Editor 3
+ none: true
+ - product: Tabular Editor CLI
+ full: true
+---
+# Interactive Mode
+
+[!INCLUDE [te-cli-preview-notice](includes/te-cli-preview-notice.md)]
+
+Interactive mode is a guided read-eval-print loop (REPL) for exploring a model from the terminal. It's the gentlest on-ramp for users who are new to command lines, and a convenient workspace for ad-hoc sessions against a single model.
+
+
+## Starting a session
+
+To Start a session run any of these commands:
+
+```bash
+te interactive # Start and connect to a model later
+te interactive ./model # Start with a local model
+te interactive -s MyWorkspace -d MyModel # Start with a remote model
+```
+
+The session prints a welcome banner, shows the active model, and opens you at a model-aware prompt:
+
+
+
+If no model is set, the prompt is just `te>` - simply use `connect` for connection picker, `connect ` or `connect ` to connect to one.
+
+## Commands inside the session
+
+Once a REPL has started, every `te` subcommand is available **without the `te` prefix**:
+
+```
+ls tables
+get "Sales/Revenue" -q expression
+query -q "EVALUATE TOPN(5, 'Sales')"
+bpa run --fail-on error
+```
+
+Each command accepts `--help` the same way it does outside the session:
+
+```
+deploy --help
+```
+
+## Quoting and DAX-style paths
+
+The REPL line splitter recognises the same quoting forms as [object paths](xref:te-cli-commands#object-paths) so DAX-shaped references are interpreted as a single argument:
+
+- `'...'` and `"..."` - single- and double-quoted segments. The quote characters are stripped, doubled quotes escape a literal occurrence.
+- `[...]` - bracketed segment. **Brackets are preserved** in the resulting argument so a path like `'Internet Sales'[Sales Amount]` reaches the command as one token that the path parser can re-interpret as a DAX reference. Doubled closing brackets (`]]`) stay verbatim for the same reason.
+
+```
+get 'Internet Sales'[Sales Amount] # One argument, DAX form
+get [Total Sales] # Lone-bracket model-wide lookup
+ls 'Net Sales'/'Sales Amount' # Quoted segments with a slash separator
+```
+
+Unterminated groups absorb to end of line, so a stray opening quote or bracket fails with an explicit error rather than splitting silently.
+
+## Built-in REPL commands
+
+These are handled by the REPL itself, not the regular command tree:
+
+| Command | Purpose |
+| -- | -- |
+| `help` or `?` | List available commands. |
+| `status` or `pwd` | Show the active model/connection. |
+| `clear` or `cls` | Clear the screen. |
+| `exit`, `quit`, or `q` | Exit interactive mode. |
+
+## Guided prompts
+
+When interactive mode is active, commands that need missing input prompt for it instead of failing. Running `auth` without a subcommand opens a picker for Login / Status / Logout; running `deploy` without `--force` shows a summary and asks for confirmation (`n` is the safe default).
+
+To disable prompts for a single command inside the session, pass `--non-interactive`.
+
+## When to use interactive vs. non-interactive
+
+- **Interactive mode** is best for exploration, learning the CLI, one-off bulk edits against a single model, and demos.
+- **Non-interactive mode** (the default outside `te interactive`) is what you reach for when scripting, automating, or running in CI. See @te-cli-automation and @te-cli-cicd.
+
+The two share the same command tree - anything you run inside `te interactive` can be pasted into a shell script by prefixing it with `te`.
+
+## Related pages
+
+- @te-cli-commands - full command reference.
+- @te-cli-auth - connect to workspaces and manage profiles.
+- @te-cli-automation - when to leave interactive mode.
diff --git a/content/features/te-cli/te-cli-limitations.md b/content/features/te-cli/te-cli-limitations.md
new file mode 100644
index 000000000..1e52055da
--- /dev/null
+++ b/content/features/te-cli/te-cli-limitations.md
@@ -0,0 +1,89 @@
+---
+uid: te-cli-limitations
+title: Known Limitations
+author: Peer Grønnerup
+updated: 2026-05-20
+applies_to:
+ products:
+ - product: Tabular Editor 2
+ none: true
+ - product: Tabular Editor 3
+ none: true
+ - product: Tabular Editor CLI
+ full: true
+---
+# Known Limitations
+
+[!INCLUDE [te-cli-preview-notice](includes/te-cli-preview-notice.md)]
+
+This page lists known limitations of the Tabular Editor CLI (`te`) so you can plan around them and avoid common pitfalls. It is updated with each release; if you find an issue that is not listed here, please file it in the public [TabularEditor/CLI](https://github.com/TabularEditor/CLI) repository.
+
+> [!NOTE]
+> Limitations are grouped by area. Each entry describes the constraint and, where one exists, a workaround or the recommended CLI-friendly alternative.
+
+## Scripting
+
+The CLI runs C# scripts (`te script`) against the same `Model` object you use in Tabular Editor 2 and 3, but it is a headless console host. Anything that depends on a Windows Forms UI, on the TOM Explorer selection, or on a live UI-side service (macro registry, online DAX Formatter, live VertiPaq Analyzer) behaves differently - usually by being empty, no-op, or returning an error.
+
+| Limitation | Notes / Workaround |
+| -- | -- |
+| **`System.Windows.Forms` not loaded** | The CLI uses a cross-platform `TOMWrapper` build that strips all WinForms-coupled code; the WinForms assembly is never loaded into the AppDomain. Scripts that reference `System.Windows.Forms` types (`MessageBox`, `Form`, file pickers, custom dialogs, …) fail to compile. Refactor any UI interaction into script arguments, environment variables, or stdin. |
+| **`Selected.` returns an empty enumerable** | `Selected.Tables`, `Selected.Measures`, `Selected.Columns`, `Selected.Hierarchies`, etc. iterate to nothing in the CLI - no compile or runtime error, just no rows. Replace with explicit lookups: `Model.AllMeasures.Where(...)`, `Model.Tables["Sales"].Measures`, or pass object paths via `te script --args`. |
+| **`Selected.` throws an error at runtime** | `Selected.Table`, `Selected.Measure`, `Selected.Column`, `Selected.Hierarchy`, etc. return an error because they require exactly one selected object of that type and the CLI selection is always empty. Reference the object directly - e.g. `Model.Tables["Sales"]`. |
+| **`Selected.ActivePerspectives` and `Selected.ActiveCulture`** | Always return an empty collection and `null` respectively. Set the perspective or culture explicitly in the script if needed. |
+| **`Select