Skip to content

Microsoft Teams browse + post

mnml-msg-teams is a terminal browser + composer for Microsoft Teams. List joined teams + channels, walk chats, search messages across Teams, and post / reply / react without leaving the keyboard. Runs standalone in any terminal.

┌─ teams ───────────────────────────────────────────────────────────────┐
│ ▸1.teams (4) 2.chats (12) 3.search (0) 4.threads (0) │
└───────────────────────────────────────────────────────────────────────┘
┌─ teams (4) ────────────────────┐ ┌─ channel ──────────────────────────┐
│ ▸ ▾ Engineering │ │ 06-07 10:14 · Alice │
│ ▸ General │ │ ship it │
│ ▸ Frontend │ │ 06-07 10:16 · Bob │
│ ▸ Design │ │ LGTM │
│ ▸ Operations │ │ [👍 3] [❤ 1] │
└────────────────────────────────┘ └────────────────────────────────────┘
1-9 tab · Enter open · / search · p post · R react · T thread · y permalink · q quit
Terminal window
cargo install --git https://github.com/chris-mclennan/mnml-msg-teams mnml-msg-teams
mnml-msg-teams --install

Microsoft Graph requires OAuth 2.0 — no “paste an API key” path. Uses the device-code flow (same as az login).

Terminal window
mnml-msg-teams auth

Prints a verification URL + one-time code, opens your browser, polls until you authenticate.

How Microsoft trusts us: ships with the public Azure CLI client ID (04b07795-8ddb-461a-bbee-02f9e1bf7b46) — same one az uses. Consent screen reads “Microsoft Azure CLI” (honest cost of not registering our own multi-tenant app).

Scopes requested: User.Read, ChatMessage.Read/Send, ChannelMessage.Read.All/Send, Channel.ReadBasic.All, Team.ReadBasic.All, Chat.Read/ReadWrite, offline_access.

Token storage: ~/.config/mnml-msg-teams/token.json (mode 0600). Access-token (~1h TTL) + refresh-token (~90d) + expiry. Proactive refresh on impending expiry; 401 triggers one-shot retry.

Revoke locally: mnml-msg-teams auth --logout.

~/.config/mnml-msg-teams/config.toml (scaffolded on first run):

refresh_interval_secs = 60
[[tabs]]
name = "teams"
kind = "teams"
[[tabs]]
name = "chats"
kind = "chats"
[[tabs]]
name = "search"
kind = "search"
kindWhat it shows
teamsJoined teams (/me/joinedTeams). Enter expands channels (lazy).
chats1:1 + group chats, newest first.
search/ to enter a query; runs POST /search/query over chatMessage.
threadsv0.2 stub — focused-thread view.
ChordAction
1-9 / TabSwitch tabs
/ k, / j, PgUp / PgDn, g / GNavigate
EnterExpand team channels · focus channel/chat · open search hit’s permalink
/Search input (search tab only)
pPost — Ctrl+S sends, Esc cancels
TThreaded reply to the top-of-scrollback message
RReact 👍 (v0.1 direct; picker in v0.2)
yYank permalink / URL / team:<id> / chat:<id>
rRefresh active tab
q / Esc / Ctrl+CQuit
Terminal window
mnml-msg-teams
:term mnml-msg-teams

Or <leader>iT after mnml-msg-teams --install.

  • Graph errors surface as graph: {code}: {msg}.
  • 429 shows Retry-After when present; no auto-retry.
  • 401 InvalidAuthenticationToken triggers one-shot refresh + retry.

The persisted token grants whatever Graph access you consented to. Treat ~/.config/mnml-msg-teams/token.json like a password. Mode 0600 by default. auth --logout deletes it.

github.com/chris-mclennan/mnml-msg-teams. MIT.