hermes-proton/ARCHITECTURE.md
Hannibal Smith 8fdf219337
architecture: Hermes-Proton multi-layer integration design
Complete ARCHITECTURE.md covering:
1. Komodo plugin spec: shared SRP-6a auth, token lifecycle, encrypted store
2. Hermes skill specs: Mail (Bridge), Pass (pass-cli), Drive (rclone), VPN (vpn-cli)
3. MCP tool server: Python stdio MCP server for non-Hermes agents
4. Auth flow: single Proton login shared across all skills
5. File layout: monorepo with plugin/skills/mcp-server/tests/
6. Environment/credential management: full env table, encrypted storage format

References: go-proton-api, pass-cli, rclone protondrive, proton-vpn-cli, hydroxide
2026-06-08 18:23:35 +02:00

35 KiB

Hermes-Proton Architecture

Multi-layer integration design — Hermes Agent runtime + Proton product suite (Mail, Pass, Drive, VPN, Calendar)

Target: Hermes Agent 0.x (komodo plugin system, Hermes skills, MCP tools) References: README.md (research report), go-proton-api, pass-cli, rclone protondrive, proton-vpn-cli


Table of Contents

  1. Project Monorepo Layout
  2. Auth Architecture — Komodo Plugin
  3. Hermes Skill: Proton Mail
  4. Hermes Skill: Proton Pass
  5. Hermes Skill: Proton Drive
  6. Hermes Skill: Proton VPN
  7. MCP Tool Server (Optional)
  8. Environment & Credential Management
  9. Auth Flow Detail
  10. Integration Layer Matrix

1. Project Monorepo Layout

hermes-proton/
├── README.md                    # Research report (already written)
├── ARCHITECTURE.md              # This file
├── plugin/                      # Komodo plugin — shared auth layer
│   ├── plugin.yaml              # Manifest
│   ├── __init__.py              # Registration: tools + hooks
│   ├── schemas.py               # Auth tool schemas
│   ├── tools.py                 # Auth tool handlers
│   ├── auth.py                  # SRP-6a client, session state machine
│   ├── store.py                 # Encrypted credential store
│   ├── token.py                 # Token lifecycle (refresh, re-auth)
│   └── config.py                # Plugin-scoped config
│
├── skills/
│   ├── proton-mail/             # Hermes skill (IMAP/SMTP via Bridge)
│   │   └── SKILL.md
│   ├── proton-pass/             # Hermes skill (pass-cli subprocess)
│   │   └── SKILL.md
│   ├── proton-drive/            # Hermes skill (rclone subprocess)
│   │   └── SKILL.md
│   └── proton-vpn/             # Hermes skill (vpn-cli subprocess)
│       └── SKILL.md
│
├── mcp-server/                  # Optional MCP server
│   ├── package.json             # Node.js / Python?
│   └── src/
│       ├── index.ts             # MCP server entry
│       ├── proton-mail.ts       # Mail MCP tools
│       ├── proton-pass.ts       # Pass MCP tools
│       ├── proton-drive.ts      # Drive MCP tools
│       └── proton-vpn.ts        # VPN MCP tools
│
├── scripts/
│   ├── install-plugin.sh        # one-command plugin install
│   └── install-skills.sh        # one-command skill install
│
├── tests/
│   ├── test_auth.py             # Auth flow tests
│   ├── test_mail.py             # Mail skill tests
│   ├── test_pass.py             # Pass skill tests
│   ├── test_drive.py            # Drive skill tests
│   └── test_vpn.py             # VPN skill tests
│
├── .env.example                 # Stub env file (no secrets)
├── .gitignore
├── LICENSE
└── hermes-proton.code-workspace # Optional VS Code workspace

Layout Rationale

Directory Purpose Why separate?
plugin/ Auth layer — SRP-6a session, encrypted store Single plugin owns shared auth; skills never touch Proton API directly
skills/ Product-specific Hermes skills Each skill maps to one Proton product; clean isolation
mcp-server/ MCP compatibility layer Optional; only needed for non-Hermes MCP clients
scripts/ Install/setup helpers One-command deployment for Hermes users
tests/ Integration & unit tests Auth tests mock the Proton API; skill tests mock subprocess

2. Auth Architecture — Komodo Plugin

2.1 Role of the Plugin

The hermes-proton komodo plugin is the single authority for Proton authentication. No skill talks directly to the Proton REST API. The plugin:

  • Handles the SRP-6a handshake once at login
  • Encrypts and stores session tokens (UID, AccessToken, RefreshToken)
  • Manages token refresh lifecycle (server-delivered + proactive refresh)
  • Provides auth context to skills via plugin-internal API
  • Detects session expiry and triggers re-authentication

2.2 Plugin Manifest (plugin/plugin.yaml)

name: hermes-proton
version: 1.0.0
description: Shared authentication layer for Proton services — SRP-6a session management, encrypted token storage, token refresh
provides_tools:
  - proton_auth_login
  - proton_auth_status
  - proton_auth_logout
provides_hooks:
  - post_tool_call
requires_env:
  - PROTON_USERNAME
  - PROTON_PASSWORD            # Only for initial login; cached after
  - PROTON_CREDENTIAL_STORE    # Optional: path to encrypted store
  - PROTON_API_URL             # Optional: default https://api.protonmail.ch

2.3 Tool Schemas (plugin/schemas.py)

proton_auth_login

PROTON_AUTH_LOGIN = {
    "name": "proton_auth_login",
    "description": (
        "Authenticate with Proton using SRP-6a. Uses PROTON_USERNAME from env. "
        "Password is prompted interactively or read from encrypted store. "
        "Returns session status. Subsequent calls use cached tokens."
    ),
    "parameters": {
        "type": "object",
        "properties": {
            "password": {
                "type": "string",
                "description": "Proton password (optional if stored)"
            },
            "force_reauth": {
                "type": "boolean",
                "description": "Force re-authentication even if tokens exist"
            }
        }
    }
}

proton_auth_status

PROTON_AUTH_STATUS = {
    "name": "proton_auth_status",
    "description": (
        "Check Proton session status. Returns whether authenticated, "
        "token expiry, and which products are available."
    ),
    "parameters": {}
}

proton_auth_logout

PROTON_AUTH_LOGOUT = {
    "name": "proton_auth_logout",
    "description": (
        "Log out of Proton. Clears cached tokens and encrypted store. "
        "Revokes refresh token with Proton API."
    ),
    "parameters": {
        "type": "object",
        "properties": {
            "revoke_all": {
                "type": "boolean",
                "description": "Revoke all sessions (remote too)"
            }
        }
    }
}

2.4 Token Lifecycle (plugin/token.py)

State machine:
┌──────────┐   first-login    ┌────────────┐
│  NO_AUTH ├─────────────────►│  AUTHED    │
└──────────┘                  └─────┬──────┘
       ▲                           │
       │       refresh fails       │ token expires
       │  ┌──────────────────┐     │
       │  │  REFRESHING      │◄────┘
       │  └────────┬─────────┘
       │           │ refresh succeeds
       │           ▼
       │    ┌────────────┐
       └────┤  AUTHED    │ (new tokens)
            └────────────┘

Refresh mechanism:

  1. Plugin hooks post_tool_call on every Proton skill call
  2. Before dispatching a tool, checks AccessToken expiry (JWT-like)
  3. If within 5 minutes of expiry, proactively refreshes via POST /api/core/v4/auth/refresh
  4. On 401 from any call, triggers immediate refresh and retries
  5. On 422 (invalid grant), transitions to NO_AUTH, blocks all skills, surfaces re-auth prompt

Storage:

  • Encrypted at rest via gopenpgp or a symmetric AES-GCM key derived from a local master password
  • Stored at $XDG_DATA_HOME/hermes-proton/session.enc by default
  • Plaintext fields: UID, AccessToken, RefreshToken, ServerProof, KeySalt, PasswordSalt, token expiry timestamps

2.5 Credential Store (plugin/store.py)

The credential store wraps an encrypted JSON file:

@dataclass
class ProtonCredentialStore:
    path: Path                    # e.g. ~/.local/share/hermes-proton/session.enc
    master_key: bytes             # Derived from PROTON_PASSWORD env or keyring

    def save(self, session: ProtonSession) -> None
    def load(self) -> ProtonSession | None
    def clear(self) -> None
    def has_valid_session(self) -> bool

Encryption scheme:

master_key = argon2id(
    password=PROTON_PASSWORD or keyring_password,
    salt=random_salt,
    time_cost=3, mem_cost=65536, parallelism=4
)
nonce = random(12)
ciphertext = aes256_gcm_encrypt(plaintext=json(session), key=master_key, nonce=nonce)
store = base64(nonce + ciphertext + tag)

2.6 Plugin API for Skills

The plugin exposes an in-process API for skills (not exposed as tools):

# Plugin internal API (consumed by skills via plugin context)
class ProtonAuthAPI:
    def get_session(self) -> ProtonSession | None
    def get_authenticated_client(self) -> proton.Client | None
    def is_authenticated(self) -> bool
    def require_auth(self) -> ProtonSession  # raises if not authenticated

Skills call require_auth() at the top of every handler. If the plugin returns a session, the skill proceeds; if not, the skill returns an error prompting the user to run proton_auth_login first.


3. Hermes Skill: Proton Mail

3.1 Strategy

Path: Proton Bridge → local IMAP/SMTP

Proton Bridge is the recommended integration vehicle for Mail. It runs as a local daemon exposing:

  • IMAP on 127.0.0.1:1143 (read)
  • SMTP on 127.0.0.1:1025 (send)
  • gRPC API on 127.0.0.1:1042 (management)
  • OpenPGP crypto is handled transparently by the Bridge — no key management in the skill

3.2 Skill Metadata

  • Name: proton-mail
  • Category: email
  • Dependencies: Proton Bridge (headless, --cli mode), Python imaplib / smtplib (stdlib), or node-imap / nodemailer if the Bridge gRPC interface is preferred
  • Auth: Relies on plugin for session; Bridge has its own local auth (TLS cert)

3.3 Tool Definitions

Tool Description Implementation
proton_mail_list List inbox messages (folder, limit, page) IMAP SEARCH / FETCH
proton_mail_read Read full message by ID IMAP FETCH (RFC822)
proton_mail_search Search messages by subject/from/body/date IMAP SEARCH (CHARSET UTF-8)
proton_mail_send Send email (to, subject, body, attachments) SMTP via localhost:1025
proton_mail_reply Reply to message by ID (thread support) SMTP + In-Reply-To/References
proton_mail_draft Save draft IMAP APPEND to Drafts
proton_mail_folders List mailbox folders IMAP LIST
proton_mail_move Move message between folders IMAP MOVE / COPY + STORE \Deleted

3.4 Implementation Pattern

def proton_mail_read(args: dict, **kwargs) -> str:
    # 1. Verify Bridge is running (systemctl check or process check)
    # 2. Connect to IMAP localhost:1143 (TLS)
    # 3. FETCH the message by UID
    # 4. Parse MIME (body text/HTML, attachments)
    # 5. Return structured JSON

3.5 Bridge Management

The skill should also expose a management tool confirming Bridge is running:

PROTON_MAIL_BRIDGE_STATUS = {
    "name": "proton_mail_bridge_status",
    "description": "Check Proton Bridge status — running, authenticated, connected to Proton servers."
}

3.6 Fallback: hydroxide

If the official Bridge is unavailable (e.g. macOS or headless server without GUI), the skill should support hydroxide (emersion/hydroxide) as an alternative:

  • Implements SRP-6a natively (no GUI required)
  • Exposes same IMAP/SMTP surface
  • Configurable via PROTON_MAIL_BRIDGE_TYPE=hydroxide env var

4. Hermes Skill: Proton Pass

4.1 Strategy

Path: Official pass-cli → subprocess with --json output

pass-cli v2.1+ supports --json output on all commands, making it trivially machine-readable. No auth management needed — pass-cli handles its own token lifecycle via its own offline-first encrypted vault.

4.2 Skill Metadata

  • Name: proton-pass
  • Category: productivity
  • Dependencies: proton-pass-cli (Rust binary, installed separately)
  • Auth: Independent — pass-cli manages its own session/session-key via proton-pass auth login

4.3 Tool Definitions

Tool Description CLI command
proton_pass_list List vaults and items proton-pass item list --json
proton_pass_get Get secret by item ID proton-pass item get <id> --json
proton_pass_search Search items by name/URL proton-pass item search <query> --json
proton_pass_create Create new password item proton-pass item create --json
proton_pass_edit Update existing item proton-pass item edit <id> --json
proton_pass_delete Delete item proton-pass item delete <id>
proton_pass_totp Get TOTP code for item proton-pass totp <id>
proton_pass_inject Inject secret as env var (SSH agent support) Reads + sets env
proton_pass_vaults List vaults proton-pass vault list --json

4.4 Implementation Pattern

def proton_pass_get(args: dict, **kwargs) -> str:
    item_id = args["item_id"]
    result = subprocess.run(
        ["proton-pass", "item", "get", item_id, "--json"],
        capture_output=True, text=True, timeout=10
    )
    if result.returncode != 0:
        return json.dumps({"error": result.stderr.strip()})
    return result.stdout  # already JSON

4.5 Security Considerations

  • Passwords returned by pass-cli enter the agent's context. This is expected (the agent needs them to use them), but users should be warned.
  • proton_pass_inject sets temporary env vars for subprocesses and scrubs them after — prevents secret leakage across turns.
  • SSH agent support via pass-cli's built-in SSH key injection.

5. Hermes Skill: Proton Drive

5.1 Strategy

Primary Path: rclone protondrive backend → subprocess
Alternative Path: Drive SDK (TypeScript, preview)

rclone's protondrive backend is the most battle-tested option. It handles Proton's non-standard API, encryption/decryption, and chunked uploads transparently. The skill shells out to rclone with protondrive remote configured.

5.2 Skill Metadata

  • Name: proton-drive
  • Category: productivity
  • Dependencies: rclone with protondrive backend configured (rclone config create protondrive ...)
  • Auth: Relies on rclone's token storage (rclone handles its own refresh)

5.3 Tool Definitions

Tool Description CLI command
proton_drive_list List files/folders in path rclone ls protondrive:path/ or rclone lsf --json
proton_drive_read Read file content (text/small binary) rclone cat protondrive:path/file
proton_drive_download Download file to local path rclone copy protondrive:path/file local/
proton_drive_upload Upload local file to Drive rclone copy local/file protondrive:path/
proton_drive_search Search files by name rclone lsf -R --files-only protondrive: | grep -i <query>
proton_drive_mkdir Create folder rclone mkdir protondrive:path/
proton_drive_delete Delete file rclone delete protondrive:path/file
proton_drive_stat Get file/bucket metadata rclone lsl protondrive:path/file
proton_drive_sync Two-way sync (with confirmation) rclone sync ...

5.4 Implementation Pattern

def proton_drive_list(args: dict, **kwargs) -> str:
    path = args.get("path", "/")
    result = subprocess.run(
        ["rclone", "lsf", "--json", f"protondrive:{path}"],
        capture_output=True, text=True, timeout=30
    )
    if result.returncode != 0:
        return json.dumps({"error": result.stderr.strip()})
    items = [json.loads(line) for line in result.stdout.strip().split("\n") if line]
    return json.dumps({"items": items})

5.5 Large File Considerations

  • For files over 10MB, proton_drive_read should stream to stdout rather than loading into memory
  • proton_drive_download targets a temp path and returns the local path
  • rclone handles chunking and retry automatically

5.6 Drive SDK Alternative

If rclone proves unreliable for specific operations (notably incremental sync or finer-grained metadata), fall back to the TypeScript Drive SDK:

# Concept: Drive SDK wrapper via node subprocess
# Only if rclone protondrive is insufficient
PROTON_DRIVE_USE_SDK = os.getenv("PROTON_DRIVE_USE_SDK", "false")

6. Hermes Skill: Proton VPN

6.1 Strategy

Path: Official proton-vpn-cli → subprocess

protonvpn-cli (Python) is the official Linux CLI. It manages its own auth (Proton API credentials stored in ~/.cache/protonvpn-cli/). The skill wraps connect/disconnect/status as tools.

6.2 Skill Metadata

  • Name: proton-vpn
  • Category: infra
  • Dependencies: protonvpn-cli (PyPI: pip install proton-vpn-gtk-app or the CLI-only variant)
  • Auth: Independent — protonvpn-cli init stores credentials

6.3 Tool Definitions

Tool Description CLI command
proton_vpn_connect Connect to fastest/selected server protonvpn-cli connect <country> or --fastest
proton_vpn_disconnect Disconnect VPN protonvpn-cli disconnect
proton_vpn_status Get connection status protonvpn-cli status --json
proton_vpn_servers List available servers/countries protonvpn-cli list --json
proton_vpn_connect_to Connect to specific server protonvpn-cli connect <server>
proton_vpn_killswitch Enable/disable kill switch protonvpn-cli killswitch <on/off>
proton_vpn_config Show current config protonvpn-cli config --json

6.4 Implementation Pattern

def proton_vpn_connect(args: dict, **kwargs) -> str:
    server = args.get("server", "--fastest")
    result = subprocess.run(
        ["protonvpn-cli", "connect", server],
        capture_output=True, text=True, timeout=30
    )
    if result.returncode != 0:
        return json.dumps({"error": result.stderr.strip()})
    return json.dumps({"status": "connected", "server": server})

6.5 Privilege Management

protonvpn-cli typically requires root or a protonvpn group membership for WireGuard interface creation. The skill should check at init:

def _check_vpn_privileges():
    user = os.getenv("USER")
    groups = subprocess.run(["groups"], capture_output=True, text=True).stdout
    if "protonvpn" not in groups and os.geteuid() != 0:
        return json.dumps({"error": "User not in protonvpn group or not root. Run: sudo usermod -aG protonvpn $USER"})
    return None

7. MCP Tool Server (Optional)

7.1 Purpose

An MCP (Model Context Protocol) server exposes Proton services to any MCP-compatible agent, not just Hermes. This includes Claude Code, Cursor, VS Code ACP, and custom MCP clients.

7.2 Architecture

                    ┌──────────────────────────────┐
                    │     MCP Client (any agent)    │
                    │  Claude Code / Cursor / etc.  │
                    └──────────┬───────────────────┘
                               │ MCP protocol (stdio or HTTP)
                    ┌──────────┴───────────────────┐
                    │   MCP Server: hermes-proton   │
                    │   (Node.js or Python)          │
                    │                               │
                    │   proton-mail-read             │
                    │   proton-drive-sync            │
                    │   proton-vpn-connect           │
                    │   proton-pass-get              │
                    └──────────┬───────────────────┘
                               │
                    ┌──────────┴───────────────────┐
                    │   Backend (same as skills)    │
                    │   Bridge / pass-cli / rclone  │
                    │   / vpn-cli / plugin auth     │
                    └──────────────────────────────┘

7.3 Tool Exports

The MCP server exposes a curated subset of skills as MCP tools:

MCP Tool Maps To Notes
proton_mail_list Mail skill → IMAP Inbox list — most useful for agents
proton_mail_read Mail skill → IMAP Full message content
proton_mail_send Mail skill → SMTP Send via Bridge
proton_mail_search Mail skill → IMAP Search mailbox
proton_pass_get Pass skill → pass-cli Get secret (caution: context exposure)
proton_pass_list Pass skill → pass-cli List without secret values
proton_drive_read Drive skill → rclone Read file content
proton_drive_list Drive skill → rclone List directory
proton_drive_search Drive skill → rclone Find files
proton_vpn_connect VPN skill → vpn-cli Connect VPN
proton_vpn_disconnect VPN skill → vpn-cli Disconnect
proton_vpn_status VPN skill → vpn-cli Status check

7.4 Implementation Options

Option A: Node.js (TypeScript)

  • Uses @modelcontextprotocol/sdk for MCP server
  • Wraps Bridge IMAP/SMTP via node-imap and nodemailer
  • Shells out to pass-cli and rclone via child_process
  • Pros: MCP SDK is first-class in Node.js ecosystem
  • Cons: Another runtime dependency

Option B: Python

  • Uses mcp PyPI package (pip install mcp)
  • Reuses Python implementations from Hermes skills directly
  • Pros: No runtime switch; skills and MCP share code
  • Cons: Python MCP ecosystem less mature than Node.js

Recommendation: Python first. The skills are written in Python. The MCP server should reuse the same code paths. If Python MCP proves problematic, fall back to Node.js.

7.5 MCP Server Design (Python)

# mcp-server/src/server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

class ProtonMCP(Server):
    def __init__(self):
        super().__init__("hermes-proton")
        self.auth = ProtonAuthAPI()
        self.mail = MailSkill(auth=self.auth)
        self.drive = DriveSkill(auth=self.auth)
        self.pass_ = PassSkill(auth=self.auth)
        self.vpn = VPNService()

app = ProtonMCP()

@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(name="proton_mail_list", ...),
        Tool(name="proton_mail_read", ...),
        # ... etc
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "proton_mail_list":
        result = app.mail.list_mail(**arguments)
        return [TextContent(content=json.dumps(result))]
    # ... etc

if __name__ == "__main__":
    import anyio
    anyio.run(stdio_server, app)

7.6 MCP Config for Hermes

When the MCP server is installed, Hermes can connect via its built-in MCP client:

# ~/.hermes/config.yaml
mcp_servers:
  proton:
    command: python
    args: ["/path/to/hermes-proton/mcp-server/src/server.py"]

Then all MCP tools appear alongside Hermes tools automatically.


8. Environment & Credential Management

8.1 Environment Variables

Variable Required Purpose Default
PROTON_USERNAME Yes Proton account email/username
PROTON_PASSWORD Optional Password (prompted if absent)
PROTON_CREDENTIAL_STORE No Path to encrypted session store ~/.local/share/hermes-proton/session.enc
PROTON_API_URL No Proton API base URL https://api.protonmail.ch
PROTON_MAIL_BRIDGE_TYPE No bridge (official) or hydroxide bridge
PROTON_DRIVE_USE_SDK No Use Drive SDK instead of rclone false
PROTON_BRIDGE_PASSFILE No Bridge password file path Auto-generated
PROTON_BRIDGE_PORT_IMAP No Bridge IMAP port 1143
PROTON_BRIDGE_PORT_SMTP No Bridge SMTP port 1025
PROTON_VPN_CLI_PATH No Path to protonvpn-cli binary Auto-detected
PROTON_PASS_CLI_PATH No Path to pass-cli binary Auto-detected
PROTON_RCLONE_REMOTE No rclone remote name for protondrive protondrive

8.2 Credential Store Design

Storage chain:
  1. PROTON_USERNAME (from env)       ← minimal env config
  2. Plugin prompts for password       ← first login
  3. Plugin encrypts tokens via AES-GCM
  4. Writes to ~/.local/share/hermes-proton/session.enc
  5. Subsequent sessions auto-load encrypted tokens
  6. Re-auth only needed on: expiry, password change, revocation

8.3 Encrypted Storage Format

{
  "version": 1,
  "created_at": "2026-06-08T...",
  "username": "user@proton.me",
  "encrypted_data": "base64...",   // AES-256-GCM encrypted payload
  "iv": "base64...",
  "salt": "base64...",
  "auth_tag": "base64..."
}

The inner plaintext (before encryption):

{
  "uid": "proton-uid",
  "access_token": "...",
  "refresh_token": "...",
  "server_proof": "...",
  "key_salt": "...",
  "password_salt": "...",
  "expires_at": 1780936666,
  "product_access": ["mail", "pass", "drive", "vpn"]
}

8.4 Multi-Account Support (Future)

The credential store supports multiple accounts via a key rotation scheme:

{
  "version": 1,
  "active_account": "user@proton.me",
  "accounts": {
    "user@proton.me": { "encrypted_data": "...", ... },
    "other@pm.me": { "encrypted_data": "...", ... }
  }
}

Skill tools accept an optional account parameter to switch contexts.


9. Auth Flow Detail

9.1 Full Login Sequence

Agent                    Plugin                         Proton API
  │                        │                               │
  │  proton_auth_login()   │                               │
  │───────────────────────►│                               │
  │                        │  GET /api/core/v4/auth/info    │
  │                        │──────────────────────────────►│
  │                        │◄──────────────────────────────│
  │                        │  {Modulus, ModulusID, ServerEphemeral,
  │                        │   Salt, Version}              │
  │                        │                               │
  │                        │  SRP client proof:            │
  │                        │  clientProof = SRP(            │
  │                        │    password, salt, modulus     │
  │                        │  )                             │
  │                        │                               │
  │                        │  POST /api/core/v4/auth       │
  │                        │  {username, clientEphemeral,  │
  │                        │   clientProof, SRPSession}    │
  │                        │──────────────────────────────►│
  │                        │◄──────────────────────────────│
  │                        │  {UID, AccessToken,           │
  │                        │   RefreshToken, ServerProof}  │
  │                        │                               │
  │                        │  Encrypt tokens, store on disk│
  │                        │  Set plugin state = AUTHED     │
  │                        │                               │
  │  {status: "authenticated", products: [...], expiry}    │
  │◄───────────────────────│                               │

9.2 Token Refresh Sequence

Skill call                         Plugin                Proton API
  │                                  │                       │
  │  skill handler calls require_auth│                       │
  │─────────────────────────────────►│                       │
  │                                  │ Check token expiry    │
  │                                  │ ├─ fresh → return     │
  │                                  │ └─ stale → refresh    │
  │                                  │                       │
  │                                  │ POST /auth/refresh    │
  │                                  │──────────────────────►│
  │                                  │◄──────────────────────│
  │                                  │ {AccessToken,         │
  │                                  │  RefreshToken, expiry}│
  │                                  │                       │
  │                                  │ Re-encrypt, store     │
  │                                  │                       │
  │  session (refreshed)             │                       │
  │◄─────────────────────────────────│                       │

9.3 Session-Expired Sequence

Skill call → require_auth()
  → plugin checks tokens → valid
  → API call returns 401
  → plugin attempts refresh → 422 (invalid grant)
  → plugin transitions to NO_AUTH
  → returns error: "Session expired. Run proton_auth_login to re-authenticate."

9.4 Auth Lifecycle Hooks

The plugin registers a post_tool_call hook to detect 401s on any skill's Proton API calls:

def _on_post_tool_call(tool_name, args, result, task_id, **kwargs):
    if result and "401" in result or "token_expired" in result:
        auth_state = _get_plugin_auth_state()
        if auth_state == "AUTHED":
            _schedule_background_refresh()
    elif result and "422" in result or "invalid_grant" in result:
        _set_auth_state("NO_AUTH")

10. Integration Layer Matrix

10.1 How Each Product Connects

Product Primary Path Auth Owner Protocol Maturity Risks
Mail Bridge (IMAP/SMTP) Bridge app password TCP + TLS Proven — OpenClaw uses this Bridge requires display (GUI) for initial login; --cli mode may be flaky
Mail (fallback) hydroxide (IMAP/SMTP) hydroxide SRP TCP + TLS Stable — 2.1k stars No official support; slower development
Pass pass-cli (Rust) pass-cli self stdio Mature — v2.1.x Secrets in agent context; SSH agent integration needs care
Drive rclone protondrive rclone OAuth-like token subprocess Beta — most-used 3rd party rclone sync semantics may not match Drive UX
Drive (alt) Drive SDK (TS) go-proton-api session HTTP Preview — breaking crypto changes Unstable API; TypeScript dependency
VPN protonvpn-cli protonvpn self subprocess Mature Requires protonvpn group / sudo
Calendar go-proton-api (direct) Plugin session HTTP Exploratory Calendar endpoints exist but underdocumented
Wallet None N/A N/A None No public API

10.2 Auth Responsibility Summary

Component Auth Type Owns Tokens? Can Re-auth?
Plugin SRP-6a Yes — master session tokens Yes — full SRP login flow
Bridge Bridge app password Yes — separate Bridge cred No — must be re-authed via Proton login
pass-cli pass-cli session key Yes — encrypted local vault Yes — proton-pass auth login
rclone protondrive token Yes — rclone config file Yes — rclone config reconnect
vpn-cli Proton API credentials Yes — ~/.cache/protonvpn-cli/ Yes — protonvpn-cli init

10.3 Which Extension Mechanism When

Scenario Use
Agent needs to read/send mail proton-mail Hermes skill
Agent needs to retrieve secrets proton-pass Hermes skill
Agent needs to access stored files proton-drive Hermes skill
Agent needs VPN connect/disconnect proton-vpn Hermes skill
Multiple skills need auth hermes-proton komodo plugin
Non-Hermes MCP agent needs Proton mcp-server with Python stdio
VS Code / Cursor agent needs Proton MCP server (same as above)

Appendix A: Risk Register

Risk Impact Mitigation Owner
Bridge requires display for initial login High — headless servers can't auth Bridge Use protonmail-bridge --cli with passfile; fallback to hydroxide Phase 2
Proton API changes (breaking) High — all products break Wrap each API call in version negotiation; log API version at login Ongoing
pass-cli secrets in agent context Medium — leakage through prompt logging Warn user; never log raw secret output; offer inject mode Phase 3
rclone protondrive backend removed Medium — Drive breaks Abstract rclone behind an interface; implement SDK fallback Phase 4
Token refresh race condition Medium — two skills refresh simultaneously Plugin uses a asyncio.Lock or threading.Lock around refresh Phase 1
VPN requires sudo Low — user friction Document protonvpn group setup in skill; check at tool dispatch Phase 5
MCP server version mismatch Low — tools drift from skills MCP server imports skills directly; always in lockstep Phase 6

Appendix B: Implementation Order

Phase 1 (Foundation)  ───►  Project scaffold + ARCHITECTURE.md
Phase 2 (Mail)        ───►  Bridge install → proton-mail skill → IMAP/SMTP tools
Phase 3 (Pass)        ───►  pass-cli install → proton-pass skill → vault tools
Phase 4 (Drive)       ───►  rclone config → proton-drive skill → file tools
Phase 5 (VPN)         ───►  vpn-cli install → proton-vpn skill → network tools
Phase 6 (Auth Plugin) ───►  Komodo plugin → SRP-6a login → encrypted store → shared session
Phase 7 (MCP Server)  ───►  Python MCP server → expose tools to non-Hermes agents
Phase 8 (Calendar)    ───►  go-proton-api Calendar endpoints (exploratory)

Auth plugin is Phase 6 (after all skills exist independently) because early phases can prototype skill-level auth independently. Plugin consolidation happens once patterns are proven.


Last updated: 2026-06-08 Author: Colonel Hannibal — A-Team Architecture