---
name: Join UC meetings
description: Use to sign in to UC Framework (University of Coimbra) and join a UC WebRTC/Janus meeting as a participant — be present, moderate, transcribe live, or speak and listen with a local voice brain.
version: 2
situations: meeting, webrtc, janus, uc framework, transcription, voice agent, moderate meeting
requires-secrets:
requires-packages: "@00/uc-meetings-cli"
---

# Join UC meetings

Drive the **`uc`** CLI (from the `@00/uc-meetings-cli` package) via your shell capability to
authenticate to UC Framework and join UC (Janus) meetings. Every command supports `--json` for
machine-readable output — **always pass `--json`** so you can parse results.

Pick the environment with `--env production|staging|development|alllocal` (default `production`).

## 1. Sign in (once)

UC binds the session token to the **User-Agent presented at login**, and the CLI replays that same UA
on every HTTP call *and* the realtime WebSocket. So **always sign in through `uc`** — a token minted
elsewhere (e.g. copied from a browser, or a raw `UC_TOKEN`) authenticates over HTTP but the realtime
gateway rejects it as `Unauthorized` unless its UA matches (the join then fails with
`InvalidConnectionID`). Password or QR login below avoids this entirely.

**Headless (recommended for agents):** if the operator stored **`UC_EMAIL`** and **`UC_PASSWORD`** as
vault secrets, they're present in your shell as `$UC_EMAIL`/`$UC_PASSWORD` and the CLI reads them
directly — no prompt, no QR:

```bash
uc login --password --json     # reads UC_EMAIL + UC_PASSWORD from the env
```

`--json` prints `{status,userKey}` on success; the session token is saved to
`~/.config/uc-cli/session.json`.

**Interactive (QR):**

```bash
uc login --json --out files/uc-login-qr.png    # writes a scannable QR PNG to files/
```

- `uc login` **saves a QR as a PNG** (default `uc-login-qr.png`; pass `--out files/…` to put it in your
  workspace) AND prints the `pngPath` in its `--json` `login` event. **Immediately call `show_files` on
  that PNG** so the operator can scan it with the **UC One** app — the terminal QR is usually too
  small/large to scan. On approval the session token is saved locally.
- Check the session anytime: `uc whoami --json`. Sign out: `uc logout --json`.
- A pre-provisioned **`UC_TOKEN`** is still honored, but for the realtime join it only works if it was
  minted under the same UA — set **`UC_USER_AGENT`** to match its origin, or (simplest) just use the
  password/QR login above.

## 2. Join a meeting

**Default — join as a GUEST using your own name** (no account needed). Use the room **alias** (the
last path segment of its `…/live/<alias>` URL). Your display name is taken **automatically from your
agent profile** (so you show up as e.g. "Otto"); pass `--name` only to override it:

> ⚠️ **`uc join` is LONG-LIVED — launch it DETACHED and read its output from a log file.** It stays
> connected for the whole meeting. If you run it in the foreground it blocks; if you background it with
> a pipe (`… | tee log &`) the bridge is **killed the moment this tool call ends** (its stdout pipe
> closes → SIGPIPE) — that is exactly why a join "enters and immediately leaves". Always launch it like
> this, then read the **log file** in later steps:

```bash
ROOM=<alias>                                   # the …/live/<alias> segment, e.g. brunosilva
LOG=~/.uc-$ROOM.log ; PIDF=~/.uc-$ROOM.pid
( nohup uc join "$ROOM" --guest --mode audio --video --json </dev/null >"$LOG" 2>&1 & echo $! >"$PIDF" )
sleep 6 ; cat "$LOG"                            # watch: authenticated → waiting → joined
```

- Name is auto-filled from your profile (you show up as e.g. "Otto"); add `--name "X"` to override.
- `--video` publishes your avatar + a live transcription tile (needs `ffmpeg`); drop it for audio-only.
- **Meeting language:** transcription defaults to **your own configured language**. If the meeting is in
  a different language, add `--meeting-lang <iso>` (e.g. `--meeting-lang es`), or `--meeting-lang multi`
  for a multilingual room where people switch languages.
- **Check on it later:** `tail -n 20 ~/.uc-<alias>.log` (a fresh bash call — the bridge keeps running).
- **Leave the meeting:** `kill "$(cat ~/.uc-<alias>.pid)"` — it disconnects cleanly (unpublishes + leaves).

The CLI mints an anonymous session (`POST /v1/anonymous/<alias>`), resolves the alias to the meeting
key, and joins. A guest lands in the **waiting room** and must be admitted by a moderator — the CLI
emits a `waiting` status, then polls every 2s (and reacts instantly to the accept signal) until
admitted or `--accept-timeout` (default 120s) elapses, then errors cleanly.

Only **sign in** (section 1) and join by meeting **key** when you specifically need host/moderator
powers or to skip the waiting room. The two participation tiers below apply to either join:

**Presence / control (default, no audio):** be a real participant that sees the live roster, talking
indicators and meeting deltas, and can moderate. Fast and dependency-free.

```bash
# also long-lived → launch it detached (see the boxed recipe above), e.g.:
( nohup uc join <meetingKey> --mode presence --auto-accept --json </dev/null >~/.uc-presence.log 2>&1 & )
```

- Emits **one JSON event per line** on stdout (status, participant, audio, meeting-delta, error).
- `--auto-accept` skips the waiting room where the meeting policy allows (joins `as_external`).
- Runs until the meeting ends or you stop it; then it leaves cleanly.

**Audio (speak + listen):** the agent hears participants (live transcription, attributed per speaker)
and speaks back. Engines are **auto-resolved** — it uses whatever is on the machine (local Voxtral /
whisper.cpp / Kokoro / Piper / macOS `say` / Ollama) or a remote API when a key exists (xAI Grok,
Voxtral via Mistral, ElevenLabs, Deepgram, OpenAI), and **downloads a local default** if a category
has nothing. On macOS the always-available offline stack is **whisper.cpp** (STT, `brew install
whisper-cpp`) + **`say`** (TTS, built-in), with the **00d agent itself** as the brain. The
**brain defaults to *this* 00d agent** (over the engine's local API), so meeting replies use the same
tools, files, skills and connectors you have.

```bash
uc engines --json               # see which stt/brain/tts resolve here (and why)
# launch it DETACHED, same as the guest recipe above (never foreground / never pipe to tee):
ROOM=<meetingKey> ; LOG=~/.uc-$ROOM.log ; PIDF=~/.uc-$ROOM.pid
( nohup uc join "$ROOM" --mode audio --video --ui --json </dev/null >"$LOG" 2>&1 & echo $! >"$PIDF" )
sleep 6 ; cat "$LOG"
```

- **Meeting voice etiquette + token saving (automatic):** replies stay short and TTS-friendly (no
  emoji/markdown). **1:1** → always answers. **Group** (2+ others) → **listens silently, spending NO
  LLM tokens**, until it hears its name (programmatic match, e.g. "Otto, summarize the meeting"); then
  it **engages for 2 min** (refreshed on each mention) before dropping back to listen-only. It ALWAYS
  transcribes to a running **transcript file** and hands the agent that file path when engaged, so it
  can read what it only listened to (e.g. to summarize). Tune: `UC_ALWAYS_REPLY_MAX` (default 1),
  `UC_ACTIVE_WINDOW_MS` (default 120000), `UC_AGENT_NAME`/`UC_AGENT_ALIASES`, `UC_VOICE_DIRECTIVE`,
  `UC_TRANSCRIPT_DIR`.
- **Realtime voice model (optional):** if a realtime speech-to-speech model is configured
  (`UC_REALTIME=xai` + `XAI_API_KEY`, model `grok-voice-latest`), it replaces the STT→brain→TTS pipeline
  for a natural low-latency voice — and it is **as capable as you**: it gets meeting context (name,
  attendees, count) and three tools — `read_meeting_transcript` (review what's been said), `ask_agent`
  (delegates to THIS 00d agent's full toolset: files, skills, memory, web, connectors), and
  `leave_meeting` (disconnect cleanly when someone asks it to go). whisper still runs locally for the
  group name-trigger. Same short/no-emoji etiquette applies.
- **Ends cleanly:** on meeting end / kick / Ctrl-C it tears down the media bridge, leaves the meeting,
  and removes its temp audio files — no lingering processes.
- `--ui` starts a local WebSocket feed (default `ws://127.0.0.1:8790`) the macOS / web app connects to
  for real-time transcription + answers. Event schema is in the package README.
- Override engines with `--brain/--stt/--tts` (or `UC_BRAIN/UC_STT/UC_TTS`); `--no-download` to skip
  auto-provisioning; `--audio-io pipe` to act only as transport for the agent's native voice mode.
- If a join has no media path (presence-only external), the CLI reports it — rejoin with `--no-external`.
- **Video feed (optional):** add `--video` to publish your avatar + a live, scrolling transcription as a
  camera tile (your name is bolded, the line that triggered you is marked). Needs `ffmpeg`.

## Sending the meeting minutes / transcript (as a file)

When someone asks you to **send / share the transcript, minutes, notes or summary** of a meeting
("send me the transcription", "send the minutes to Ana on Signal", "email João the notes"), do **not**
paste the whole thing into chat. Produce a proper document and send it as a **file attachment**:

1. **Read the running transcript file** — its path is in the meeting context / the `transcript …` status
   line. It records speech AND presence changes (`→ joined` / `← left`), timestamped, with the meeting
   name + start time in the header.
2. **Compile a Markdown minutes document** with, in order: a title + the **meeting name + date**; a
   **Participants** list; a **Timeline** of who joined/left and when; a short **Summary** of what was
   discussed plus any **decisions / action items**; then the **full transcript**.
3. **Save it as an `.md` file** in your workspace (e.g. `files/minutes-<meeting>-<YYYY-MM-DD>.md`).
4. **Send it as an attachment** on the requested channel — put the file path (or a URL) in the message's
   media/attachment, not the body. (Signal attaches local files and now also downloads media URLs, so
   either works; the message still goes through your normal outgoing approval.)

## 3. Moderate

```bash
uc meeting accept <key> <attendeeKey...> --json   # admit from waiting room
uc meeting mute   <key> --all --json              # or: mute <key> <attendeeKey>
uc meeting kick   <key> <attendeeKey> --json
uc meeting end    <key> --json
```

## Notes

- If a command reports it is not signed in, run `uc login --password --json` when the `UC_EMAIL`/
  `UC_PASSWORD` secrets exist (headless), otherwise `uc login` (QR) and show the operator the PNG.
- Parse the `--json` stdout; human-readable lines go to stderr.
- The `uc join` event stream is the same data the UI renders — you can react to `transcript` and
  `participant` events to decide when to speak or moderate.
