@m8t-stack/cli
v0.2.5
Published
CLI for managing m8t-stack deployments on Azure / Microsoft Agent Foundry
Readme
@m8t-stack/cli — the m8t CLI
📖 Reference for the
m8tcommand-line tool that manages m8t-stack deployments.
What it is
The m8t CLI manages a deployed m8t-stack platform: identity (whoami, status, doctor), config (config, switch), team membership (team), channel bindings (bind), the gateway stack (deploy), hosted coders (coder), and brains (brain). It's published to npm, Homebrew, and Scoop.
Why / how it fits
It's the operator's day-2 tool for the cloud platform — the successor to the old deploy/setup.mjs. It shares @m8t-stack/api-contract types with the gateway at build time and authenticates with your az session (DefaultAzureCredential).
Install
# npm (any OS with Node 20+)
npm install -g @m8t-stack/cli
# Homebrew (macOS / Linux)
brew install m8t-run/tap/m8t
# Scoop (Windows)
scoop bucket add m8t https://github.com/m8t-run/scoop-bucket
scoop install m8t
# Verify
m8t versionThe CLI bundles the shared workspace types into a single dist/cli.js, so the
npm install is self-contained (only the external runtime deps resolve from npm).
Publishing is automated by .github/workflows/release-cli.yml (OIDC trusted
publishing); see publish/npm-trusted-publishing-setup.md.
Build from source (contributors)
pnpm --filter "@m8t-stack/cli" build # produces dist/cli.js with shebang
cd apps/cli && pnpm pack # produces m8t-stack-cli-<version>.tgz
npm install -g ./m8t-stack-cli-*.tgzUsage
m8t version # CLI version
m8t whoami # Identity + which gateway you're talking to (probes the backend)
m8t status # Full local snapshot: az identity, config.yaml, gateway cache, azd mode
m8t doctor # Health checks (az · config · tenant align · gateway · Foundry data-plane · model-quota) + fixes
m8t doctor --agent <name> # + delivery-grant check: verifies the agent's MI holds Key Vault Secrets User
m8t open [webapp|foundry|portal] # Open the deployed app / Foundry portal / resource group in a browser
# Local config (~/.m8t-stack)
m8t config show # config.yaml (tenant/client/project) + the cached gateway
m8t config set <key> <value> # set tenantId | clientId | projectEndpoint
m8t config reset # clear the gateway discovery cache
m8t switch --subscription <id|name> # re-point local config at another deployment (snapshots first)
m8t switch <profile> # restore a saved profile; m8t switch --list
# Team management
m8t team add <handle> --display "<name>" [--telegram <id>] [--slack <id>] [--teams <upn>]
m8t team list
m8t team show <handle>
m8t team add-identity <handle> [--telegram <id>] [--slack <id>] [--teams <upn>]
m8t team remove-identity <handle> --telegram|--slack|--teams
m8t team remove <handle>
# Deploy / update the gateway stack (replaces the old deploy/setup.mjs)
m8t deploy [--client-id <appId>] [--image-ref <ref>] [--location <region>] [--resource-group <name>]
# Brain management
m8t brain link <name> --repo <owner>/<repo> [--persona <path>] [--allow-non-reasoning]
# --persona: override the recorded persona file (or use when no local agent yaml exists)
# --allow-non-reasoning: suppress the reasoning-model warning--subscription <id> flag
Available on all gateway-using commands (whoami, config show, config reset,
team add, team list, team show, team add-identity, team remove-identity,
team remove, bind add, bind list, bind show, bind remove, bind cleanup).
Pins gateway discovery to the named subscription, bypassing the active az
subscription. Useful when you manage multiple m8t-stack deployments across
subscriptions or when your shell's active subscription differs from the deployment
target.
m8t team list --subscription <subscription-id-or-name>Local context & config — status / doctor / switch
The CLI reads two local files under ~/.m8t-stack/: config.yaml (your tenantId, clientId, and Foundry projectEndpoint — used by the web app + architect) and cli-config.yaml (the auto-discovered gateway URL cache).
m8t status— a read-only snapshot of everything local: your signed-inazidentity + active subscription/tenant,config.yaml(tenant / client / project), the gateway cache, theazdauth mode, and whether youraztenant matchesconfig.yaml(tenant alignment).--output jsonfor scripting.m8t doctor— runs health checks and prints a fix for each failure; exits non-zero if any check FAILs. Checks:azsigned in ·config.yamlvalid · tenant alignment · gateway reachable (/api/me) · Foundry data-plane reachable (a 401/403 prints the exactaz role assignment create … "Cognitive Services User"command) · model-quota check (warns for any deployed model with 0 TPM quota in the region). Results stream as each check runs. Pass--agent <name>to add a targeted delivery-grant check: reads the agent's deployed env and verifies its managed identity holdsKey Vault Secrets Useron the configured vault.m8t config set <key> <value>— writetenantId,clientId, orprojectEndpointintoconfig.yaml(validated).m8t config shownow displaysconfig.yamlalongside the gateway cache.m8t switch— re-point your local config at another deployment in one step.--subscription <id|name>discovers the gateway there and derives everyconfig.yamlvalue from its container env (zero manual IDs);<profile>restores a saved profile;--listlists profiles. Every switch snapshots the outgoing config to a profile first (label it with--as <name>), so it's reversible — e.g.m8t switch --subscription <new> --as work, then laterm8t switch personal.
Open in the browser — m8t open
m8t open # the deployed webapp (gateway URL) — default
m8t open foundry # the Microsoft Foundry portal (ai.azure.com)
m8t open portal # the resource group in the Azure portal
m8t open --print # print the URL instead of launching a browser (pipe-friendly)Deploy / update the gateway — m8t deploy
Provisions (or updates) the gateway/webapp stack via Bicep — the successor to the old node deploy/setup.mjs. See deploy/README.md for the full flow.
# Bring-your-own app registration (required if you can't create Entra app regs, e.g. a directory guest):
m8t deploy --client-id <appId> --image-ref <acr-or-ghcr-ref> --location <region>Key flags: --client-id <appId> (reuse an existing app reg + skip all Microsoft Graph writes), --image-ref <ref> (full image ref; default ghcr.io/m8t-run/m8t:latest — pass an *.azurecr.io/... ref for ACR images), --resource-group (default rg-m8t-stack), --location, --suffix, --foundry-endpoint, --foundry-resource-id.
⚠️
m8t deployis declarative — it makes the live stack match the Bicep template. If a deployment was hand-modified after provisioning (e.g. switched to a private-ACR image with a UserAssigned pull identity), re-running the bicep can revert those changes. Preview withaz deployment group create --what-ifbefore applying against a live stack.
Binding management — m8t bind
Wire channel bots (Telegram, Slack, Teams) to Foundry workers. F4 ships the management API + CLI scaffolding; the actual adapters (which deliver inbound messages from each channel) ship in later features.
# Create a binding (requires the channel adapter — Telegram ships in F6)
m8t bind add telegram \
--worker cmo \
--bot-token <from-botfather> \
--slug cmo-tg \
--bot-username "@startup_cmo_bot"
# List all bindings, optionally filtered
m8t bind list
m8t bind list --channel telegram
m8t bind list --status orphaned
# Inspect one binding (token NEVER returned — only the KV URI)
m8t bind show cmo-tg
# Remove a binding (cascade: unregister webhook → KV → conversations → row)
m8t bind remove cmo-tg
m8t bind remove cmo-tg --yes # skip confirm
# Orphan-only cleanup (refuses to act on active bindings)
m8t bind cleanup <bindingId>
m8t bind cleanup --all-orphaned
--workeris the case-sensitive, lowercase Foundry agent name as deployed (e.g.cmo). A wrong name isn't rejected at bind time — it surfaces later as an orphaned binding on the first message.
Pre-F6 behavior
m8t bind add telegram ... currently fails with:
error: no adapter for channel 'telegram'. This is expected for F4 alone;
the channel-specific adapter ships in a later feature.This is correct. F6 (Telegram adapter) will register the adapter at startup and unlock the create flow.
Cascade-delete idempotency
If m8t bind remove fails mid-cascade, it prints which steps completed
and which step failed, and recommends re-running the command. Each step
is independently idempotent — a re-run resumes safely from the failure
point.
Output modes
Pretty table or key-value block by default; pipe or redirect, and the CLI auto-switches to JSON:
m8t team list # pretty
m8t team list | jq # auto-JSON
m8t team list --output json # explicit JSON
m8t team list --output pretty # force pretty even on pipeExit codes
0success1any failure (auth, network, validation, backend)2user-cancelled
Granular error info in stderr's structured JSON envelope.
Prerequisites
- Node 20+
az loginagainst the customer's Azure tenant. CLI usesDefaultAzureCredential— no per-admin pre-registration.
Troubleshooting
error: You are not signed in to Azure.
Run az login first. The CLI uses your active az session to acquire a token for the gateway.
error: No m8t-stack deployment found in subscription <name>.
The deployment is in a different subscription. Switch with:
az account set --subscription <other-sub>
m8t config reset
m8t team listerror: Multiple m8t-stack deployments found ...
You have more than one gateway in this subscription. The CLI prompts you to pick one when running interactively (e.g. m8t whoami). The choice is cached at ~/.m8t-stack/cli-config.yaml. Or pass --subscription <id> to target a specific subscription and skip the picker entirely.
(gateway is starting up...)
The deployed gateway is configured with min-replicas=0 (scale-to-zero). First request after idle pays ~10–20s cold start. The CLI retries once on 502/503/504.
error: partial write — <channel-A> succeeded, <channel-B> failed.
A multi-identity m8t team add partially succeeded. Run m8t team show <handle> to verify the current state, then m8t team add-identity <handle> --<channel-B> <id> to fill in the missing identity.
CLI version vs gateway version drift
The CLI and the gateway share @m8t-stack/api-contract types at compile time (the CLI bundles them at build). If the gateway is on an older version and returns a response in an unknown shape, the CLI surfaces BAD_RESPONSE with a "this may be a gateway version mismatch" hint.
