Skip to content

Latest commit

 

History

History
324 lines (247 loc) · 13.1 KB

File metadata and controls

324 lines (247 loc) · 13.1 KB

Architecture

This document describes the architecture of outlook-cli, covering the shared configuration format, the Node.js and .NET implementations, the security model, and AI agent integration.

Overview

outlook-cli has two independent implementations that produce identical CLI behavior:

Node.js .NET
Language JavaScript (ESM) C# (.NET 8)
CLI framework Commander.js System.CommandLine (beta4)
Auth library @azure/msal-node Microsoft.Identity.Client (MSAL.NET)
Graph calls Direct HTTP (fetch) Direct HTTP (HttpClient)
Test runner vitest Integration tests via vitest
AOT N/A (interpreted) NativeAOT (~10 MB binary)

Both implementations share all configuration files and the encrypted token cache. You can authenticate with one and use the other.

Shared Configuration

All configuration is stored in ~/.outlook-cli/.

accounts.json

Defines accounts, their Azure app registrations, and per-account permissions:

{
  "defaults": {
    "permissions": {
      "allowed": ["User.Read", "Mail.Read", "Mail.ReadWrite", "Calendars.Read", "Calendars.ReadWrite", "offline_access"],
      "forbidden": ["Mail.Send"]
    }
  },
  "accounts": {
    "personal": {
      "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "tenant": "common",
      "parent": null
    },
    "work": {
      "clientId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
      "tenant": "contoso.onmicrosoft.com",
      "parent": "personal",
      "permissions": {
        "send_to": ["team@contoso.com", "boss@contoso.com"]
      }
    }
  },
  "default": "personal"
}

Key concepts:

  • defaults block — Shared permission settings inherited by all accounts
  • parent chains — An account can inherit settings from a parent account
  • per-account permissions — Override allowed/forbidden scopes and send_to whitelists

aliases.json

Maps short names to email addresses:

{
  "fred": "freddie@outlook.com",
  "wilma": "wilma@outlook.com",
  "boss": "jane.smith@contoso.com"
}

Aliases can be used anywhere an email address is expected (e.g., --to fred, --as fred).

Token Cache

  • Format: AES-256-GCM encrypted binary
  • Key derivation: PBKDF2 with SHA-512, 310,000 iterations
  • Passphrase source: OUTLOOK_CLI_PASSPHRASE environment variable, or machine-derived default
  • Files: token-cache-<account>.bin
  • Interoperable: Both Node.js and .NET read/write the same format

Delta Tokens

Watch mode stores delta tokens for change tracking:

  • Files: delta-<alias>.json
  • Content: Microsoft Graph delta link URLs for resuming change queries

Microsoft Graph API

Both implementations call the Microsoft Graph REST API v1.0 directly via HTTP — no SDK wrapper.

Endpoints Used

Feature Method Endpoint
Inbox GET /me/mailFolders/inbox/messages
Read message GET /me/messages/{id}
Search GET /me/messages?$search=...
Create draft POST /me/messages
Reply POST /me/messages/{id}/createReply
Reply all POST /me/messages/{id}/createReplyAll
Forward POST /me/messages/{id}/createForward
Send POST /me/messages/{id}/send
Move POST /me/messages/{id}/move
Flag PATCH /me/messages/{id}
Mark read PATCH /me/messages/{id}
Mail folders GET /me/mailFolders
Calendar view GET /me/calendarView
Event details GET /me/events/{id}
Create event POST /me/events
List calendars GET /me/calendars
Contacts GET /me/people?$search=...
Delta (mail) GET /me/mailFolders/inbox/messages/delta
Delta (calendar) GET /me/calendarView/delta

For delegate access (--as), the /me/ prefix is replaced with /users/{userId}/.

Output Pipeline

Both implementations use a render dispatcher pattern:

Command result → Render dispatcher → Format module → stdout/file
                                     ├── text.js / Text.cs
                                     ├── json.js / Json.cs
                                     ├── markdown.js / Markdown.cs
                                     └── html.js / Html.cs

The --format flag (or --json shorthand) selects the output module. The --output flag redirects to a file.

Security Model

See SECURITY.md for the full threat model. Key architectural elements:

Configurable Permissions

Each account in accounts.json can define:

  • allowed — Scopes the CLI will request during authentication
  • forbidden — Scopes that must NOT appear in the token (enforced at runtime)
  • send_to — Whitelist of recipient email addresses for send operations

Defense-in-Depth Token Validation

Even if the Azure app registration is misconfigured, the CLI inspects the acquired token and rejects it if:

  1. It contains a forbidden scope
  2. A send operation targets a recipient not in the send_to whitelist (when configured)

Confirmation Prompts

All write operations (draft, reply, forward, send, move, flag, create event) require interactive confirmation. The --yes flag bypasses prompts for scripting and agent use.

Node.js Specifics

  • Module system: ESM ("type": "module" in package.json)
  • CLI: Commander.js defines commands and options declaratively
  • Auth: @azure/msal-node handles OAuth 2.0 authorization code flow, device code flow, and silent token refresh
  • HTTP: Native fetch() (Node.js 20+) for Graph API calls
  • Testing: vitest with mock-based unit tests

See src-node.md for build and development details.

.NET Specifics

  • Framework: .NET 8 with NativeAOT compilation
  • CLI: System.CommandLine (2.0.0-beta4) for command parsing
  • Auth: MSAL.NET (Microsoft.Identity.Client) for OAuth 2.0 flows
  • HTTP: HttpClient for Graph API calls
  • Serialization: All JSON handled via source generators (OutlookCliJsonContext) — no reflection at runtime
  • Graph client design: GraphClient accepts pre-serialized string? bodyJson parameters rather than generic objects, ensuring AOT compatibility
  • Binary size: ~10 MB self-contained, no .NET runtime required on target

See src-dotnet.md for build and publish details.

Watch Mode

Watch mode uses Microsoft Graph delta queries for efficient change tracking:

  1. First call returns all current items + a delta token
  2. Subsequent calls send the delta token and receive only changes since the last call
  3. Delta tokens are persisted in ~/.outlook-cli/delta-<alias>.json

Rules Engine

The --config flag accepts a JSON file defining rules for processing incoming changes:

{
  "jobs": [
    {
      "account": "work",
      "folder": "Inbox",
      "rules": [
        {
          "match": { "from": "*@contoso.com" },
          "action": { "type": "webhook", "url": "https://..." }
        }
      ]
    }
  ]
}

See WATCH-MODE.md for full documentation.

Dual-Mode Claw Integration

outlook-cli integrates with OpenClaw and NanoClaw AI agent frameworks in two modes:

Skill Mode

The agent invokes CLI commands and parses structured JSON output:

Agent → outlook-cli mail inbox --json → JSON response → Agent processes

The skill/ directory contains tool definitions that describe available commands, parameters, and response formats for the agent framework.

Channel Factory Mode

Watch mode acts as a channel factory — it normalizes incoming emails into channel messages and delivers them to the agent:

Graph delta → Watch rules engine → Normalize to channel message
                                    ├── stdout (JSONL)
                                    ├── webhook (HTTP POST)
                                    ├── exec (spawn process)
                                    └── file (append to file)

This allows the agent to receive a stream of normalized events without polling.

COM Bridge Provider (Enterprise)

For enterprise environments where Microsoft Graph API is blocked or unavailable (e.g., Exchange on-premises with no Graph API access, or corporate policies that prevent Azure App Registrations), outlook-cli can route requests through a COM Bridge server running on a Windows machine with Outlook Desktop installed.

Dual-Provider Architecture

The .NET implementation supports two providers — the Graph API (default) and the COM Bridge:

┌───────────────────────────────────────────────────────────────┐
│                       outlook-cli                             │
│                                                               │
│  ProviderFactory → provider = "graph"?                        │
│                    ├── Yes → GraphMailProvider → Graph API     │
│                    └── No  → BridgeMailProvider → HTTP+HMAC   │
│                                                    │          │
│                              ┌─────────────────────▼────────┐ │
│                              │    COM Bridge Server         │ │
│                              │    (separate Windows host)   │ │
│                              │    ASP.NET Core + COM interop│ │
│                              │    → Outlook Desktop (COM)   │ │
│                              └──────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
Provider Use Case Auth Transport
Graph API (default) Cloud-connected, standard M365 environments OAuth 2.0 via MSAL HTTPS to graph.microsoft.com
COM Bridge Enterprise with Graph API blocked, on-premises Exchange HMAC-SHA256 shared secret HTTP to bridge server

Bridge Configuration

Set provider: "bridge" in accounts.json to route an account through the COM Bridge:

{
  "accounts": {
    "work-bridge": {
      "clientId": "not-used-for-bridge",
      "provider": "bridge",
      "bridgeUrl": "http://bridge-machine:9080",
      "bridgeSecret": "your-shared-hmac-secret"
    },
    "shared-mailbox": {
      "clientId": "not-used-for-bridge",
      "provider": "bridge",
      "bridgeUrl": "http://bridge-machine:9080",
      "bridgeSecret": "your-shared-hmac-secret",
      "bridgeAccount": "shared"
    }
  }
}

The bridgeAccount field maps to an account entry in the bridge server's bridge.json, routing requests to a specific Outlook store (mailbox). Omit it to use the default store.

Bridge Implementation

  • Bridge server: src/bridge/ — C# ASP.NET Core, Windows-only. Wraps Outlook Desktop via COM automation (OutlookWrapper.cs). Not NativeAOT (COM requires reflection).
  • CLI providers: src/dotnet/Providers/BridgeMailProvider.cs, BridgeCalendarProvider.cs, BridgeContactsProvider.cs — HTTP clients that call the bridge server with HMAC-signed requests.
  • Provider routing: src/dotnet/Providers/ProviderFactory.cs — reads provider field from account config and creates the appropriate provider.
  • Security: src/bridge/Security/BridgeSecurity.cs — HMAC-SHA256 request signing, nonce-based replay protection (60s window), read-only mode support.

Bridge Capabilities

Feature Graph API COM Bridge Notes
Mail list/read/search Bridge uses Outlook's MAPI search
Mail draft/send/reply/forward Bridge validates send controls
Mail move/flag/mark-read/delete
Mail folders
Calendar events/create/delete
Calendar list calendars ⚠️ Bridge returns hardcoded "default" — COM has no equivalent
Contacts search Bridge searches GAL via CreateRecipient+Resolve and AddressEntries iteration, plus personal contacts folder. Graph uses People API.
Delegate access (--as) Not supported via COM — use multi-account instead
Multi-account (store routing) Bridge routes via ?account= param to specific Outlook stores
Watch mode (delta queries) Bridge uses poll-based checking
Attachments

See self-hosting.md for deployment instructions.

Related Documentation