Architecture
High Level Architecture
LazyTeams follows a Clean Architecture design with SRP enforcement. Packages are isolated and interact only via dependency injection. The UI layer uses Elm Architecture (Model-Update-View) via Bubble Tea.
Package Structure
lazyteams/
├── main.go # Entry point
├── cmd/
│ └── auth-helper/ # Playwright auth helper (captures tokens & cookies)
│ ├── main.go # Capture/renew logic + CLI flags
│ ├── console_windows.go # ANSI/VT + UTF-8 setup on Windows
│ └── console_other.go # No-op console setup on Linux/macOS
└── internal/
├── auth/ # Tokens (reading + JWT parsing)
│ └── auth.go # GetTokens(), loadTokensFile(), ParseUserNameFromToken()
├── graph/ # HTTP client for Microsoft Graph API
│ ├── client.go # Client, doReq(), cleanHTML()
│ ├── urls.go # Microsoft host allowlist (token exfiltration guard)
│ ├── teams_api.go # Teams and channels
│ ├── chats_api.go # Chats, auto-discovery self-chat
│ ├── messages_api.go # Read/write messages (ChatSvc)
│ ├── files_api.go # Drive browser, remote items
│ ├── upload_api.go # File uploading chunked sessions
│ ├── download_api.go # File downloads
│ ├── ams_api.go # Azure Media Services (Inline image pasting)
│ ├── presence_api.go # Get/Set/Clear presence
│ ├── assignments_api.go # Education Assignments (blocked by WAF)
│ ├── activity_api.go # Notifications via ChatSvc
│ ├── auth_check.go # Lightweight token health checks
│ └── debug_api.go # Debug endpoints
├── helpers/ # Shared cross-domain helpers
│ └── fs.go # ConfigDir(), HomeDir(), Process management
├── teams/teams.go # Attachment aggregation, file icons
├── version/ # Version string + GitHub update check
│ ├── version.go # Version string & comparison
│ ├── update.go # API client for GitHub releases + SHA-256 verification
│ ├── replace_unix.go # Atomic executable replacement for POSIX
│ └── replace_windows.go # Detached executable replacement for Windows
└── ui/ # TUI (Elm Architecture with Bubble Tea)
├── components/ # Reusable sub-components
│ └── directorypicker/ # Interactive filesystem browser
├── clipboard.go # Native OS clipboard image extraction
├── commands.go # tea.Cmd constructors (API calls)
├── format.go # Message formatting logic
├── helpers.go # Pure utility functions
├── help.go # Cheat sheet renderer
├── model.go # Application state
├── prefs.go # Persistent preferences on disk
├── styles.go # Lipgloss styles
├── symbols.go # UI unicode symbols and icons
├── types.go # Shared type definitions
├── update.go # Main state machine
├── update_data.go # Network/API response handling
├── update_insert.go # Textarea input logic
├── update_keys.go # Keybinding routing
├── update_popups.go # Floating windows logic
└── view.go # Rendering logic (Layouts, Panels)Design Principles
Clean Architecture
The ui package never makes HTTP calls. All network requests pass through graph and teams via tea.Cmd (async commands). This enforces a strict separation of concerns.
Elm Architecture (Model-Update-View)
Every UI component follows the Elm pattern:
- Model: Holds the entire application state
- Update: Pure function that handles events and returns new state + commands
- View: Pure function that renders the current state to the terminal
4 Workspaces
The application is organized into 4 workspaces, each with a left panel (list) and right panel (detail):
Auth Helper — How It Works
The helper uses Playwright with Firefox to capture tokens automatically:
- Persistent session (
~/.config/lazyteams/browser-session/): after the first login, subsequent runs skip credentials. - Request interceptor (
context.On("request")): captures every Bearer token Teams sends, discriminating byaud(JWT claim). - Automatic navigation: the helper navigates through Teams to trigger requests containing the required tokens.
- Graph Explorer: at the end of the loop, if
MS_GRAPH_TOKENis missing, opens Graph Explorer and captures the token from the interceptor or DOM.
Key Discoveries During Development
- Teams v2 uses
substrate.office.comas a Graph proxy — it does not make direct requests tograph.microsoft.com - The
audfor Microsoft Graph is the GUID00000003-0000-0000-c000-000000000000, not the stringgraph.microsoft.com TEAMS_NOTIF_TOKENandTEAMS_WEB_TOKENare the same token (interchangeable)- MSAL does not persist refresh tokens in IndexedDB — tokens live only in JS memory