> ## Documentation Index
> Fetch the complete documentation index at: https://docs.komaa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Install the plugin, connect it to StandIn, and place a test Teams call.

Pick your runtime. Both paths get you to a live Teams call where your agent answers in voice.

## Prerequisites

* A working **OpenClaw** or **Hermes Agent** install (the host).
* A **StandIn** account ([standin.komaa.com](https://standin.komaa.com)) - the hosted media bridge that
  joins the Teams call and connects to your plugin. No worker to run. A paid plan is not required to
  start: the **free identity** gives you one BYO-bot identity with 5 minutes of call time per day included.
* Your own **Microsoft Teams bot** (an Azure AD app) connected to StandIn - see the
  [Teams setup](/teams/overview) to create and upload it.

<Note>
  This quickstart is the **standard setup**: you connect your **own** Microsoft Teams bot and callers dial it. To try
  StandIn with **no Azure bot** (a shared bot joins a public meeting you generate), use the
  [Sandbox (community tier)](/community) instead. The sandbox is metered: 5 minutes of bot time per day per room,
  up to 5 new rooms per day, one bot per meeting, and a 7-day room link, with the call ending at the daily limit.
</Note>

* A realtime voice provider key (for **realtime** mode) - e.g. OpenAI or Azure OpenAI. Streaming mode
  uses your host's configured STT/TTS instead.

## Install (one line)

The installer detects your **OpenClaw** or **Hermes** install, adds the matching plugin, and prompts
for the settings (mode, shared secret, provider API key + model, inbound call policy). On a re-run
every prompt defaults to your current configuration, so pressing Enter keeps each value.

**Linux / macOS / WSL**

```bash theme={null}
curl -fsSL https://standin.komaa.com/install.sh | bash
```

**Windows (PowerShell)**

```powershell theme={null}
irm https://standin.komaa.com/install.ps1 | iex
```

It also runs non-interactively - pass flags (`--runtime`, `--mode`, `--secret`, `--provider`,
`--api-key`, `--model`, `--port`) or set the matching `STANDIN_*` environment variables.

<Note>
  For **secrets** (`--secret`, `--api-key`), prefer the `STANDIN_SECRET` / `STANDIN_API_KEY` environment variables over
  CLI flags, so the values do not land in your shell history. The installer never transmits your secrets: they are only
  written to your local plugin config.
</Note>

## Connect your agent by pairing (recommended)

Pairing is the zero-config way to connect a **subscribed** identity: the installer exposes your voice
endpoint, sends StandIn the exact `wss://host:<voicePort>` URL, and delivers the generated shared secret back
to the installer automatically. There are **no ports to configure** and nothing to copy by hand, which avoids
the most common first-call issue (a domain saved without its voice port, so StandIn dials the wrong port and
the bot silently never joins).

<Steps>
  <Step title="Run the installer and choose Pair">
    Run the [one-line installer](#install-one-line) on the machine your agent runs on. When it asks
    **"Pair this agent with StandIn now?"**, choose **Pair**. It prints an 8-character code.
  </Step>

  <Step title="Enter the code at standin.komaa.com/pair">
    Sign in at [standin.komaa.com/pair](https://standin.komaa.com/pair) and enter the code. StandIn shows you
    the exact voice endpoint and port your agent proposed - confirm it and approve. If you run the official
    **msteams** channel plugin, the installer already collected your Azure bot App ID, client secret, and
    tenant from its config and sent them with the pairing, so the credential fields arrive pre-filled (the
    secret is consumed server-side and never shown in the browser). Otherwise the installer prompts you for
    them, or you type them here.
  </Step>

  <Step title="Done">
    The installer picks up the shared secret on its next poll and finishes. Your identity provisions and
    StandIn dials your agent - you never type a voice URL, a port, or a secret.
  </Step>
</Steps>

<Note>
  At the approve step you choose what the pairing becomes: a **paid identity** (consumes a plan slot), a
  **free identity** (your own Azure bot, 5 minutes/day included, one per account), or the **sandbox**
  (no Azure bot at all - a shared StandIn bot joins a meeting StandIn generates; see the
  [Sandbox (community tier)](/community)).
</Note>

## Free identity vs Sandbox: which am I?

Two free ways to run StandIn - pick by whether you have an Azure bot:

* **You run the official `msteams` channel plugin** (so you already have an Azure Bot App ID + secret;
  this is the normal OpenClaw/Hermes Teams journey): use a **Free identity**. It is the same BYO-bot setup
  as paid - your own bot, your own tenant, inbound calls to your own Teams identity - with
  **5 minutes of call time per day** included, one free identity per account, held alongside any paid identities.
* **You have no Azure bot** (you never installed the msteams channel plugin and just want to hear your
  agent talk): use the **[sandbox (community tier)](/community)**. A shared StandIn bot joins a public
  meeting StandIn generates - zero Azure setup.

<Warning>
  If you already run the Teams `msteams` plugin, use a Free identity instead of the sandbox - the sandbox
  joins a **second** (shared) bot, so two bots will be in the meeting, both wired to the same agent.
</Warning>

<Note>
  **First chat message ignored?** The two runtimes gate DMs differently. **OpenClaw** drops DMs from
  unapproved senders **silently** by default (`dmPolicy: pairing`) - approve yourself once with
  `openclaw pairing approve msteams <CODE>`. **Hermes** replies to an unknown sender with a
  **pairing code** instead of silence - approve it with `hermes pairing approve teams <CODE>`; only
  when `TEAMS_ALLOWED_USERS` is set are unlisted senders dropped silently. See
  [Troubleshooting](/troubleshooting#the-bot-answers-calls-but-ignores-teams-chat-dm-pairing).
</Note>

## Verify (optional)

Prefer not to pipe a script straight into your shell? Download it, review it, then run it:

<Tabs>
  <Tab title="macOS / Linux">
    Download:

    ```bash theme={null}
    curl -fsSL https://standin.komaa.com/install.sh -o install.sh
    ```

    Review it:

    ```bash theme={null}
    less install.sh
    ```

    Run it:

    ```bash theme={null}
    bash install.sh
    ```
  </Tab>

  <Tab title="Windows (PowerShell)">
    Download:

    ```powershell theme={null}
    irm https://standin.komaa.com/install.ps1 -OutFile install.ps1
    ```

    Review it:

    ```powershell theme={null}
    notepad install.ps1
    ```

    Run it:

    ```powershell theme={null}
    ./install.ps1
    ```
  </Tab>
</Tabs>

Always fetch over HTTPS from `standin.komaa.com`. The installer never transmits your secrets; they
are only written to your local plugin config.

## Manual install

Prefer to wire it up by hand? Pick your runtime:

<Tabs>
  <Tab title="OpenClaw (npm)">
    <Steps>
      <Step title="Install">
        ```bash theme={null}
        openclaw plugins install npm:@komaa/openclaw-msteams-bridge
        ```

        The package ships prebuilt (v0.1.10+), so there is no build step - install and go.

        <Note>
          Also on ClawHub: `openclaw plugins install clawhub:@komaa/openclaw-msteams-bridge` (OpenClaw falls back to
          npm automatically if the ClawHub fetch fails). See the
          [plugin README](https://github.com/komaa-com/openclaw-msteams-bridge#install).
        </Note>
      </Step>

      <Step title="Configure">
        Add a `msteams-voice` entry to your OpenClaw config - `sharedSecret` must match the value you set in StandIn:

        ```jsonc theme={null}
        {
          "plugins": {
            "entries": {
              "msteams-voice": {
                "config": {
                  "enabled": true,
                  "mode": "realtime",
                  "port": 9442,
                  "path": "/voice/msteams/stream",
                  "sharedSecret": "<same secret as in your StandIn dashboard>",
                  "inboundPolicy": "allowlist",
                  "allowFrom": ["<caller AAD object id>"],
                  "realtime": {
                    "provider": "openai",
                    "providers": {
                      "openai": { "apiKey": "<key>", "model": "gpt-realtime" }
                    }
                  }
                }
              }
            }
          }
        }
        ```

        Full reference → [OpenClaw configuration](/openclaw/configuration).
      </Step>

      <Step title="Run + call">
        Start the gateway (`openclaw gateway run`), then in your StandIn dashboard register your
        **Agent voice URL** (`wss://<your-public-host>:8443/voice/msteams/stream` via the funnel; local bind `ws://<host>:9442/...`) and the matching secret. Place a Teams call to your bot.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Hermes (pip)">
    <Steps>
      <Step title="Install">
        Into the **same Python environment as Hermes**:

        ```bash theme={null}
        uv pip install --python /path/to/hermes/venv/bin/python "hermes-msteams-bridge[documents]"
        ```

        The `[documents]` extra enables `show_file` document rendering and the
        progress panel. Enablement happens in the config below: `teams_call`
        under `plugins.enabled` is all Hermes needs.
      </Step>

      <Step title="Configure">
        In `~/.hermes/config.yaml` under `plugins.entries.teams_call.config` (secrets in `.env`):

        ```yaml theme={null}
        plugins:
          enabled: [teams_call]
          entries:
            teams_call:
              config:
                shared_secret: ${TEAMS_CALL_SHARED_SECRET}   # must match the secret set in StandIn
                host: 127.0.0.1
                port: 8443
                # Inbound caller policy: Hermes has no `inboundPolicy` key and no
                # pairing mode for calls - the allowlist IS the policy. Empty means
                # deny all inbound callers (set `allow_all: true` to opt out).
                allowlist: ["<caller AAD object id>"]
                realtime:
                  backend: azure
                  azure_endpoint: https://<your-azure-resource>.cognitiveservices.azure.com
                  azure_deployment: gpt-realtime
                  api_key: ${AZURE_FOUNDRY_API_KEY}
        ```

        Full reference → [Hermes configuration](/hermes/configuration).
      </Step>

      <Step title="Run + call">
        ```bash theme={null}
        hermes teams-call serve --handler realtime
        ```

        In your StandIn dashboard, register your **Agent voice URL** (`wss://<your-public-host>:8443/voice/msteams/stream` via the funnel; local bind `ws://<host>:8443/...`)
        and the matching secret, then place a Teams call to your bot.
      </Step>
    </Steps>
  </Tab>
</Tabs>

<Warning>
  **StandIn connects to your plugin from the internet**, so its WebSocket must be reachable - expose it
  via a public URL or a tunnel (e.g. Tailscale), not loopback-only. Default ports **9442** (OpenClaw) /
  **8443** (Hermes); path `/voice/msteams/stream`. A `sharedSecret` mismatch fails the HMAC handshake
  **silently** - the most common first-call issue.
</Warning>

<Tip>
  Streaming mode (no realtime key needed) is a one-line change - see [Modes](/concepts/modes).
</Tip>
