Teams with Super Powers
  • C# 99.1%
  • Just 0.9%
Find a file
2026-08-20 16:01:47 -04:00
assets Project icons and images 2026-08-20 16:01:47 -04:00
data Include data documentation 2026-08-18 16:36:00 -04:00
specs Project icons and images 2026-08-20 16:01:47 -04:00
src Project icons and images 2026-08-20 16:01:47 -04:00
.gitignore Initial implementation (spec-01 / OPUS-5) 2026-08-18 15:25:45 -04:00
AGENT.md Initial implementation (spec-01 / OPUS-5) 2026-08-18 15:25:45 -04:00
justfile History viewer implementation (Opus 4.8) 2026-08-19 11:58:10 -04:00
LICENSE Project icons and images 2026-08-20 16:01:47 -04:00
README.md Project icons and images 2026-08-20 16:01:47 -04:00
Ubermannschaft.sln History viewer implementation (Opus 4.8) 2026-08-19 11:58:10 -04:00

Ubermannschaft — Super Teams

Ubermannschaft

A local tool that syncs your Microsoft Teams conversations into a portable SQLite database.

Connecting without an Azure app registration

The linked guide assumes you can register an Azure application, and most people cannot. Getting Graph access on a locked-down tenant turned out to need two fallbacks; the tool now handles both.

Why the obvious routes don't work here

The supported route is Microsoft Graph via the device code flow against a Microsoft-published public client — no app registration needed. On this tenant that hits two walls in turn:

  • AADSTS50105 — the admin blocked the Graph command-line app for unassigned users. That block is per-application, so the tool tries other first-party apps (Azure CLI, Office, Teams) too.
  • AADSTS65002 — but those apps aren't preauthorized to call Graph with Teams scopes like Chat.Read. Only the blocked app was. So device sign-in cannot work here at all.

How it connects instead: reuse the Teams client's own token

The Teams desktop client signs in normally — its app is preauthorized for those scopes — and Windows caches the Microsoft Graph tokens it obtains in the local token broker (%LOCALAPPDATA%\Microsoft\TokenBroker\Cache), sealed with DPAPI for your account. The tool reads that cache, decrypts the tokens you already hold, and reuses them against Graph. No sign-in, no consent prompt, no app registration — it sees exactly what you see in Teams, nothing more.

Different Teams sub-apps cache different scopes, so the tool picks the right cached token per request (a channel-capable token for channel messages, a chat token for chats, and so on). Because Teams keeps refreshing these tokens, a long-running monitor stays authenticated as long as Teams is installed and signed in. If a needed token has expired and Teams hasn't refreshed it, the tool asks you to open Teams.

Run just login to confirm it can connect and see which capabilities are available:

account   you@example.com
channels  available
╭─────────────────────┬─────────────────────────┬───────────╮
│ Capability          │ Scope                   │ Available │
├─────────────────────┼─────────────────────────┼───────────┤
│ chats & group chats │ Chat.Read               │ yes       │
│ channel messages    │ ChannelMessage.Read.All │ yes       │
│ …                   │                         │           │
╰─────────────────────┴─────────────────────────┴───────────╯

Choosing the method

AuthMode in data/settings.json (or appsettings.json) controls this:

Value Behaviour
auto Reuse Teams' tokens on Windows; fall back to device sign-in elsewhere. (Default)
broker Always reuse Teams' cached tokens. Windows + Teams only.
device Always use the interactive device code flow (needs a tenant that allows it).

In device mode you can force a specific client with just login --client teams (also azure-cli, office, graph-cli), or pin your own registration with { "ClientId": "<guid>" }.

Note on token reuse: it reads an undocumented Windows cache, so it is Windows-only and may break with a future Teams or Windows update. It never sends your tokens anywhere — they are used only for your own Graph requests and are not copied into the database.

Running it

just login              # sign in and confirm which client your tenant accepts
just monitor            # backfill, then keep watching
just sync-once          # one pass and exit
just viewer             # open the desktop app to browse and search messages
just status --chats     # what is stored locally, no network
just logout             # forget cached tokens

Without just, the same thing directly:

dotnet run --project src/Ubermannschaft/Ubermannschaft.csproj -- monitor

First run prints a device code; open the URL, enter the code, and approve the sign-in. Tokens are cached in data/token-cache.bin so later runs are silent.

What it does

On startup it scans everything not yet persisted, then stays resident and polls for new activity every 60 seconds (--interval to change). Graph change notifications would need a public HTTPS endpoint that a machine-local tool cannot offer, so the monitor polls; repeat passes use Graph delta queries, so they only transfer what changed.

Per pass it walks:

  • every one-on-one chat, group chat and meeting chat (/me/chats), including membership;
  • every channel of every team you have joined (/me/joinedTeams), including reply threads;
  • every message body, sender, edit and deletion;
  • every attachment — files via the Graph /shares endpoint, inline images and code snippets via hostedContents.

Browsing and searching

just viewer opens a native Windows desktop app (WPF, in src/Ubermannschaft.Viewer) over the database — read-only, so it is safe to browse while the monitor keeps syncing.

  • Global search (top bar): type a term and press Enter. Results are cards, one per matching message, showing the conversation and a preview with the term highlighted, most recent first, case-insensitive. Click a card to jump to that message in context.
  • Conversation list (left): chats first, then teams & channels, most recent at the top. The box above filters the list as you type. Click a conversation to open it, newest message at the bottom; scroll up to load earlier messages.
  • Find in conversation: press Ctrl+F while a conversation is open. Enter or ↑ / Ctrl+Up searches into the past, ↓ / Ctrl+Down searches toward the present; the match is highlighted and centered. Esc closes the find bar.

The message-body HTML is rendered as plain text, so nothing from a message can execute, and remote images are never fetched.

Data layout

data/ubermannschaft.sqlite3, lower-case tables:

Table Purpose
chats One row per chat, group chat or channel: type, topic, team/channel, delta link.
chat_members The users in each chat.
messages Message bodies, senders, timestamps, edits and deletions.
attachments One row per attachment, referencing its message and its file on disk.
sync_state Bookkeeping (signed-in account, last completed pass).

Attachment bytes land in data/attachments/{chat}/{message}/; attachments.local_path holds the path relative to data/. Every row also keeps the original Graph JSON in a raw_json column, so nothing is lost if a field is needed later.

Permissions

The tool only ever uses access you already have. In token-reuse mode that is whatever the Teams client was granted (typically chats, channels, and files all together). In device mode, chats need only permissions you can consent to yourself (Chat.Read, Files.Read.All, Sites.Read.All), while channels additionally need ChannelMessage.Read.All, which requires an administrator. When a capability isn't available the tool says so and continues with what it can reach — it does not fail.

Configuration

src/Ubermannschaft/appsettings.json holds the defaults. Override them per machine by creating data/settings.json with the same keys, or with UBERMANNSCHAFT_-prefixed environment variables (for example UBERMANNSCHAFT_PollIntervalSeconds=30).

Key Default Meaning
AuthMode auto auto, broker (reuse Teams tokens), or device (sign in).
ClientId (empty) Device mode: pin sign-in to one app (GUID or alias). Empty = auto-discover.
ClientCandidates four first-party apps Device mode: the apps auto-discovery tries, in order.
TenantId organizations Tenant to sign in against.
PollIntervalSeconds 60 Wait between passes.
SyncChannels true Include teams and channels.
DownloadAttachments true Fetch attachment bytes, not just metadata.
MaxAttachmentBytes 100 MB Skip anything larger. 0 disables the limit.
ChannelReplyLookbackDays 30 How far back to re-check channel reply threads.

Projects

Project What it is
src/Ubermannschaft The ubermannschaft CLI: monitor, login, status, logout.
src/Ubermannschaft.Viewer The WPF desktop viewer launched by just viewer.

Requirements

.NET 10 SDK, and just for the shortcuts. Open Ubermannschaft.sln in Visual Studio 2026. The viewer is a WPF app, so it builds and runs on Windows.

License

Licensed under the GNU Affero General Public License v3.0. Because the AGPL's network clause applies, anyone you offer this to over a network is entitled to its corresponding source.