Skip to content

adstuart/copilot-teams-bridge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Copilot Teams Bridge

Talk to GitHub Copilot CLI on your dev machine — from Microsoft Teams on your phone. ~400 lines of JS. No cloud infra. No webhooks.

Architecture

Architecture diagram

Why?

If you're working on a public or private GitHub repo, the official GitHub Copilot app for Teams and Copilot coding agent are excellent — they run in the cloud against your repo and handle PRs, issues, and code review natively. Use those.

This bridge is for everything else — when you need Copilot to access your local filesystem, private infrastructure, internal tooling, build systems, or anything that doesn't live in a GitHub repo. Your phone becomes a thin client for your entire dev environment — with full context, local auth, and persistent sessions.

  • 💬 Natural conversation — Send any message in Teams. Get a Copilot response back. Session persists across messages — it remembers context.
  • 📊 Live progress — Milestone-driven updates show what Copilot is actually doing — not just "Working..." spam. Tunable frequency and cap.
  • Fastpath shortcutszzz file issue for flaky test bypasses Copilot entirely and drops a draft item on your GitHub Projects board in <1 second.
  • 🛑 Mid-run cancel — Send cancel to abort a long-running job. The bridge polls for commands while Copilot runs.
  • 📱 Phone-optimised — A mobile brevity hint biases Copilot toward short, scannable responses. Commands work without slash prefixes.
  • 🔐 Your own identity — Runs under your own Entra app registration with minimal scopes. MFA-enforced. No shared secrets, no third-party services.

The bridge is deliberately simple:

  1. A systemd service runs on your dev machine (Linux VM, WSL, bare metal — anything that runs Node.js)
  2. It polls Microsoft Graph every 5 seconds for new messages in a dedicated Teams DM chat
  3. New messages are passed to GitHub Copilot CLI as prompts, with session continuity
  4. Copilot's response is posted back to the same Teams chat
  5. Special commands (cancel, reset, zzz, etc.) are intercepted before reaching Copilot

No webhook endpoint. No Cloudflare Tunnel. No cloud functions. No Bot Framework. Just polling + child process + systemd.

copilot-teams-bridge/
├── src/
│   ├── index.mjs            # Main loop: polling, commands, progress, replies
│   ├── teams-client.mjs     # Microsoft Graph auth & chat API
│   ├── copilot-client.mjs   # Spawns Copilot CLI, streams JSON output
│   └── github-projects.mjs  # zzz fastpath → GitHub Projects GraphQL
├── config.json               # Your configuration
├── config.example.json       # Template
└── package.json

Commands

All commands work with or without a leading /. Case-insensitive.

Command What it does
help Show all available commands
status Bridge health, auth status, token expiry, uptime
cancel Abort the running Copilot job mid-execution
last Resend the most recent successful answer
reset Clear Copilot session — next message starts fresh
stop Stop the bridge (systemd restarts it automatically)
zzz <text> Quick-capture a draft item to GitHub Projects board

Setup

Prerequisites

  • Node.js 18+
  • GitHub Copilot CLI installed and authenticated
  • An M365 tenant with two user accounts (one for the "bot", one for you)
  • An Entra app registration (public client, delegated permissions: Chat.ReadWrite, ChatMessage.Send, ChatMessage.Read, User.Read)

1. Clone and configure

git clone https://github.com/adstuart/copilot-teams-bridge.git
cd copilot-teams-bridge
cp config.example.json config.json
# Edit config.json with your tenant, client ID, recipient UPN, etc.

2. Authenticate

node src/index.mjs
# Follow the device code prompt — sign in as your bot account
# Tokens are cached locally and refresh automatically

3. Run as a service (optional)

# Copy the systemd unit file
sudo cp copilot-teams-bridge.service /etc/systemd/system/
sudo systemctl enable --now copilot-teams-bridge

4. Open Teams and start chatting

Pin the DM chat with your bot account. Send any message. Copilot responds.

Progress UX

Long-running tasks show milestone updates streamed from Copilot's intermediate reasoning — not generic "Working..." spam:

Progress UX on iPhone

Updates are throttled (configurable interval and cap) so they inform without flooding your phone notifications.

How it compares

Approach Local tools? Persistent sessions? Phone UX? Setup complexity
This bridge ✅ Full access ✅ Per-thread ✅ Optimised Low — config + systemd
GitHub Copilot for Teams (official) ❌ Cloud only Minimal
Copilot coding agent (cloud) ❌ Repo-scoped ✅ Per-issue ✅ Via GitHub Minimal
Squad Moderate — Teams app + AI orchestration
VS Code + SSH/tunnel ❌ Not phone-friendly Moderate
Bot Framework bot High — Azure hosting, bot registration, webhook endpoint
WhatsApp (Baileys/unofficial) Low but no chat isolation — bot sees all your messages

Configuration

Key Description Default
tenantId Your M365 Entra tenant
clientId Your Entra app client ID
recipientUpn UPN of the account you message from
chatId Pin to a specific chat (optional) Auto-created DM
copilotTimeoutSeconds Max time for a single Copilot run 3600 (60 min)
pollIntervalMs How often to check for new messages 5000
progressUpdateMinIntervalMs Min gap between progress updates 20000
maxProgressUpdates Cap on progress messages per job 8
mobileSystemPrompt Brevity hint prepended to prompts "Reply will be read on a phone..."
projectCapturePrefix Fastpath trigger for GitHub Projects zzz
githubProjectId GitHub Projects v2 node ID

Security

Reduced permission scope

Many Teams-to-AI tutorials use Microsoft Graph Explorer's client ID for quick demos. That app has dozens of pre-registered scopes — Mail, Files, Calendar, Directory, Sites, and more. A leaked token from that app can access everything.

This bridge uses its own Entra app registration with only the 5 delegated permissions it needs:

Permission Why it's needed
Chat.ReadWrite Access the dedicated DM chat
ChatMessage.Send Post replies back to Teams
ChatMessage.Read Read incoming commands/prompts
User.Read Resolve the bot's own identity
offline_access Refresh token (auto-renew auth)

No mail. No files. No calendar. No directory. If a token is ever compromised, the blast radius is limited to chat messages in chats the bot account is a member of — nothing else.

Defence in depth

  • MFA enforced via Security Defaults on the M365 tenant.
  • Dedicated accounts — the bot and receiver are single-purpose M365 identities with no other roles or data.
  • No secrets in code — public client flow (PKCE). No client_secret to leak. Tokens cached locally with filesystem permissions.
  • Auth health monitoring — the status command shows token expiry, refresh state, and consecutive failures in real time.
  • No inbound connectivity — the bridge polls outbound. No open ports, no webhook endpoints, no attack surface from the internet.

Acknowledgements

This project was inspired by and built alongside exploration of several community approaches:

  • Squad by Brady Gaster — Teams adapter for collaborative AI workflows. Different goal (team collaboration vs. personal bridge) but the initial inspiration for "Teams + Copilot".
  • Copilot CLI by the GitHub Copilot team — the engine under the hood. The --output-format json flag makes streaming progress possible.
  • Tamir Dresher's work on Squad and community discussions around Teams as an AI surface.
  • The broader community building personal AI assistants on messaging platforms (Baileys, MoltBot, etc.) — different transports, same philosophy.

FAQ

Why Teams and not WhatsApp / Slack / Telegram?

Teams gives channel isolation. The bot account only sees the dedicated chat. WhatsApp unofficial libraries (Baileys) expose all your personal messages to the bot. Slack and Telegram are viable alternatives with similar API patterns — PRs welcome.

Why polling instead of webhooks?

Zero infrastructure. No public endpoint, no DNS, no TLS cert, no tunnels. The bridge runs behind NAT/firewall with no inbound connectivity needed. 5-second polling latency is imperceptible for this use case.

Will this break if I don't touch it for months?

The refresh token has a 90-day sliding window and gets renewed every ~1 hour while the bridge runs. As long as your machine is up and the M365 tenant is active, auth rolls indefinitely.

Can I use this without a separate M365 tenant?

Yes — you just need two accounts in any M365 tenant where you can register an Entra app. A personal dev tenant works, or your org tenant if you have app registration permissions.

License

MIT


Built with GitHub Copilot. The entire bridge — including this README — was developed through conversational iteration with Copilot CLI.

About

Teams ↔ Copilot CLI bridge — talk to your local dev machine from your phone

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors