Your AI powerhouse — every model, every device, your server. Self-hosted multi-model AI chat powered by the official GitHub Copilot SDK.
The only open-source web UI built on the official @github/copilot-sdk — exposing the full power of Copilot Agents in a browser, on any device.
Access GPT-4.1, o-series, Claude, and Gemini through a single GitHub login. Run autonomous agents in autopilot mode. Use every GitHub MCP tool. Bring your own tools. Keep your sessions alive forever. All from your phone.
Disclaimer: This is an independent, community-driven project — not an official GitHub product. Use at your own risk.
GitHub Copilot is extraordinarily powerful — but most of that power is locked inside VS Code or the terminal. You can't use autopilot mode from your phone. You can't switch between GPT-4.1 and Claude in the same conversation. You can't share a Copilot-powered agent with your team without everyone installing the CLI. You can't connect custom tools or call your internal APIs.
This project unlocks all of it.
| Copilot Web Chat | Copilot in VS Code | Copilot CLI | Copilot CLI Mobile | |
|---|---|---|---|---|
| Works on mobile | — | — | — | ✅ |
| All models (GPT-4, o-series, Claude, Gemini) | Partial | Partial | ✅ | ✅ |
| Autopilot agent mode | — | — | ✅ | ✅ |
| Extended thinking / reasoning traces | — | Partial | ✅ | ✅ |
| GitHub MCP tools | Partial | Partial | ✅ | ✅ |
| Custom MCP servers | — | ✅ | ✅ | ✅ |
| Custom webhook tools | — | — | — | ✅ |
| File attachments | ✅ | ✅ | — | ✅ |
| Persistent sessions + resume | — | — | — | ✅ |
| Self-hosted / team deployment | — | — | — | ✅ |
| Your data stays on your server | — | — | — | ✅ |
Commuting? Waiting in line? Use the built-in GitHub MCP tools to browse open pull requests, read diffs, understand what changed, and get a plain-language summary — no laptop, no IDE.
"Summarize the changes in PR #312 and tell me if there are any security concerns"
→ Copilot fetches the PR, reads every file diff, and reports back with a structured review.
Switch to autopilot mode and give Copilot a complex task: implement a feature, write tests, refactor a module. The agent plans, executes shell commands and tool calls, and reports back. You monitor from your phone. No need to babysit a terminal.
"Implement the password reset flow as described in issue #88. Write tests and open a PR when done."
→ Copilot reads the issue, creates a plan, writes code, runs tests, and opens the PR — autonomously.
Stop switching between tabs. Ask a hard architecture question, then switch the model mid-conversation:gpt-4.1 for speed → o3 for deep reasoning → claude-sonnet for a creative angle. All history preserved, all responses in one view.
One subscription. Four frontier models. Zero tab switching.
Paste a gnarly stack trace and switch to o3 or claude-sonnet with xhigh reasoning effort. Watch the live reasoning trace — the model "thinks out loud" in a collapsible block before giving its answer. You see how it reaches the conclusion, not just what it concludes.
Define custom webhook tools in the settings UI. Copilot can now call your internal APIs, query your database, look up a Jira ticket, trigger a deployment, or read from a private data source — as part of its agentic workflow.
"Check if ticket PROJ-1234 is still open, then search GitHub for any related PRs"
→ Copilot calls your Jira webhook, reads the ticket, then uses GitHub MCP to search for PRs.
Run azd up once. Everyone on the team opens the URL, logs in with their own GitHub account, and gets their own isolated Copilot sessions — no shared API keys, no shared context. The server handles everything.
Start debugging a gnarly issue on Monday morning at your desk. Close the laptop. On Tuesday, open the same session from your phone and pick up exactly where you left off — full history, full context, infinite sessions with automatic compaction.
GITHUB_CLIENT_ID + SESSION_SECRETGitHub account with a Copilot license (free tier works)
GitHub OAuth App — register one in 30 seconds:
http://localhost:3000GITHUB_CLIENT_IDDevice Flow auth — no client secret needed, no redirect URI to configure.
Add GITHUB_CLIENT_ID as a Codespace secret scoped to this repo
Launch:
Done — the app builds and opens automatically.
# Create .env
echo "GITHUB_CLIENT_ID=<your-client-id>" >> .env
echo "SESSION_SECRET=$(openssl rand -hex 32)" >> .env
docker compose up --build
Open localhost:3000 — enter the code on GitHub, start chatting.
Requires Node.js 24+ (the SDK needs node:sqlite).
npm install && npm run build && npm start
npm run dev:local # Local Vite dev server (hot reload)
npm run dev # Docker Compose (full stack)
npm run check # Type check (svelte-check)
npx playwright test # E2E tests (desktop + mobile viewports)
| Variable | Required | Default | Description |
|---|---|---|---|
GITHUB_CLIENT_ID |
Yes | — | GitHub OAuth App client ID |
SESSION_SECRET |
Yes | — | Session encryption key (openssl rand -hex 32) |
PORT |
— | 3000 |
HTTP server port |
BASE_URL |
— | http://localhost:3000 |
App URL for cookies + WebSocket origin validation |
NODE_ENV |
— | development |
production enables secure cookies + trust proxy |
ALLOWED_GITHUB_USERS |
— | — | Comma-separated allowlist of GitHub usernames |
TOKEN_MAX_AGE_MS |
— | 86400000 |
Force re-auth interval in ms (default: 24h) |
| Layer | Technology |
|---|---|
| Runtime | Node.js 24 (node:24-slim in Docker) |
| Language | TypeScript 5.7 (strict mode, ES2022) |
| Framework | SvelteKit 5 with adapter-node |
| Reactivity | Svelte 5 runes ($state, $derived, $effect, $props) |
| AI Engine | @github/copilot-sdk ^0.1.32 |
| Build | Vite 7 → build/ via adapter-node |
| WebSocket | ws ^8.18 via custom server.js |
| Markdown | marked ^17 + dompurify + highlight.js (npm, bundled by Vite) |
| Sessions | express-session bridged to SvelteKit via x-session-id header |
| Testing | Playwright (desktop + mobile viewports) |
| Container | Multi-stage Dockerfile (node:24-slim) |
| IaC | Bicep (Container Apps, ACR, Managed Identity, Log Analytics, App Insights) |
| CI/CD | GitHub Actions (ci.yml + deploy.yml) |
Browser (Svelte 5) ──WebSocket──▶ SvelteKit + server.js ──JSON-RPC──▶ @github/copilot CLI subprocess
│ │ │
│ SSR + Device Flow auth │ Session pool per user │ Copilot API
│ Rune-based reactive stores │ SDK event forwarding │ GitHub MCP tools
▼ ▼ ▼
CopilotClient with its own CLI subprocess| Feature | SDK API | UI |
|---|---|---|
| Model selection | SessionConfig.model + client.listModels() |
Bottom sheet, mid-session switching |
| Reasoning effort | SessionConfig.reasoningEffort |
Color-coded toggle (low / medium / high / xhigh) |
| Streaming | assistant.message_delta events |
Token-by-token with typing cursor |
| Extended thinking | assistant.reasoning_delta / reasoning |
Collapsible live reasoning block |
| Modes | session.rpc.mode.set() |
Three-button toggle (ask / plan / auto) |
| Custom instructions | SessionConfig.systemMessage (append) |
Settings accordion textarea |
| GitHub MCP | SessionConfig.mcpServers |
All GitHub tools with readonly toggle |
| Custom MCP servers | SessionConfig.mcpServers |
Add / update / delete external MCP servers |
| Tool lifecycle | tool.execution_start/progress/complete |
Animated spinner + checkmark |
| User input | onUserInputRequest callback |
Choice buttons + freeform input |
| Permission hooks | onPermissionRequest hook |
Allow / Deny / Always Allow with countdown |
| File attachments | session.send({ attachments }) |
📎 button, file chips, upload flow |
| Custom tools | defineTool() |
Webhook editor in settings |
| Infinite sessions | SessionConfig.infiniteSessions |
Configurable compaction thresholds |
| Session management | client.listSessions/deleteSession |
Bottom sheet with resume + delete |
| Subagents | subagent.started/completed/selected/deselected |
Agent display with lifecycle status |
| Token usage | assistant.usage |
Token counts after each response |
| Abort | session.abort() |
Stop button during streaming |
| Context compaction | session.rpc.compaction.compact() |
Settings button + auto events |
| Plan management | session.rpc.plan.* |
Collapsible view/edit panel |
| Elicitation | elicitation.requested/completed |
Structured input prompts |
| Quota | client.getQuota() |
Quota dot indicator |
| Skill invocation | skill.invoked |
Skill status display |
Client → Server (21 types):
| Type | Purpose |
|---|---|
new_session |
Create session with model, reasoning, instructions, tools, attachments |
message |
Send user prompt (max 10,000 chars) with optional file attachments |
list_models |
Fetch available models |
set_mode |
Switch: interactive / plan / autopilot |
set_model |
Change model mid-session |
set_reasoning |
Update reasoning effort |
abort |
Cancel streaming response |
user_input_response |
Reply to SDK prompt |
permission_response |
Reply to tool permission request |
list_tools / list_agents |
Discover available tools and agents |
select_agent / deselect_agent |
Manage active agents |
get_quota / compact |
Usage info and context compaction |
list_sessions / resume_session / delete_session |
Session history |
get_plan / update_plan / delete_plan |
Plan management |
Server → Client (46+ types):
| Type | Source | Purpose |
|---|---|---|
connected |
— | Connection ready, includes GitHub username |
session_created / session_reconnected |
— | Session lifecycle |
delta |
assistant.message_delta |
Streamed token |
reasoning_delta / reasoning_done / reasoning_changed |
assistant.reasoning_* |
Thinking traces |
intent |
assistant.intent |
Model's inferred intent |
turn_start / turn_end |
assistant.turn_* |
Turn lifecycle |
tool_start / tool_progress / tool_end |
tool.execution_* |
Tool lifecycle |
mode_changed / model_changed |
— | Setting confirmations |
title_changed |
session.title_changed |
Auto-generated title |
usage / context_info |
assistant.usage |
Token counts and context info |
warning / error |
session.* |
Session alerts |
subagent_start / subagent_end / subagent_failed |
subagent.* |
Agent lifecycle |
subagent_selected / subagent_deselected |
subagent.* |
Agent selection |
info / plan_changed / skill_invoked |
session.* |
Informational |
user_input_request |
onUserInputRequest |
SDK asks for input |
permission_request |
onPermissionRequest |
Tool permission prompt |
sessions / session_resumed / session_deleted |
— | Session management |
tools / agents / agent_changed / quota |
— | Discovery & status |
plan / plan_updated / plan_deleted |
session.plan_* |
Plan management |
elicitation_requested / elicitation_completed |
elicitation.* |
Structured prompts |
exit_plan_mode_requested / exit_plan_mode_completed |
— | Mode transitions |
compaction_start / compaction_complete / compaction_result |
session.compaction_* |
Context management |
models |
client.listModels() |
Model list |
done / aborted |
— | Response lifecycle |
BASE_URL in productionALLOWED_GITHUB_USERSserver.js # Custom entry: HTTP + express-session + WebSocket + SvelteKit handler
svelte.config.js # SvelteKit config (adapter-node)
vite.config.ts # Vite config
tsconfig.json # TypeScript config (strict, ES2022)
src/
├── app.html # SvelteKit shell (viewport, theme-color, PWA meta)
├── app.css # Global reset, design tokens, highlight.js theme
├── hooks.server.ts # Session bridge, CSRF, CSP, security headers, rate limiting
├── lib/
│ ├── components/ # 18 Svelte 5 components
│ │ ├── Banner.svelte # Info/warning banners
│ │ ├── ChatInput.svelte # Message input + file attachments
│ │ ├── ChatMessage.svelte # Single message rendering
│ │ ├── CustomToolsEditor.svelte # Webhook tool editor
│ │ ├── DeviceFlowLogin.svelte # GitHub Device Flow login UI
│ │ ├── EnvInfo.svelte # Environment info display
│ │ ├── MessageList.svelte # Scrollable message list
│ │ ├── ModelSheet.svelte # Model selection bottom sheet
│ │ ├── PermissionPrompt.svelte # Tool permission allow/deny
│ │ ├── PlanPanel.svelte # Plan view/edit panel
│ │ ├── QuotaDot.svelte # Quota usage indicator
│ │ ├── ReasoningBlock.svelte # Collapsible reasoning trace
│ │ ├── SessionsSheet.svelte # Session history bottom sheet
│ │ ├── SettingsModal.svelte # Settings with instructions, tools, MCP
│ │ ├── Sidebar.svelte # Navigation sidebar
│ │ ├── ToolCall.svelte # Tool execution display
│ │ ├── TopBar.svelte # Top navigation bar
│ │ └── UserInputPrompt.svelte # SDK input request UI
│ ├── stores/ # Svelte 5 rune stores (factory functions)
│ │ ├── auth.svelte.ts # Device flow state, polling, countdown
│ │ ├── chat.svelte.ts # Messages, streaming, tool calls, models, plan
│ │ ├── settings.svelte.ts # Persisted preferences (localStorage)
│ │ └── ws.svelte.ts # WebSocket connection + typed send helpers
│ ├── server/ # Server-only code
│ │ ├── config.ts # Environment config (fail-fast validation)
│ │ ├── security-log.ts # Security event logging
│ │ ├── session-store.ts # Express session bridge store
│ │ ├── auth/
│ │ │ ├── github.ts # Device Flow OAuth
│ │ │ ├── guard.ts # Auth guard middleware
│ │ │ └── session-utils.ts # Session utility helpers
│ │ ├── copilot/
│ │ │ ├── client.ts # CopilotClient factory
│ │ │ └── session.ts # Session config builder
│ │ └── ws/
│ │ ├── handler.ts # WebSocket: 21 client + 27 SDK events
│ │ └── session-pool.ts # Per-user session pool with TTL
│ ├── types/index.ts # All types: 46+ server + 21 client message types
│ └── utils/markdown.ts # Markdown pipeline (marked + DOMPurify + hljs)
├── routes/
│ ├── +page.svelte # Main: login or chat (wires all components)
│ ├── +layout.server.ts # Root: auth check from session
│ ├── +layout.svelte # Root layout
│ ├── +error.svelte # Error page
│ ├── auth/
│ │ ├── device/start/ # Start Device Flow
│ │ ├── device/poll/ # Poll for token
│ │ ├── logout/ # Logout endpoint
│ │ └── status/ # Auth status check
│ ├── api/
│ │ ├── models/ # List available models
│ │ ├── upload/ # File upload handler
│ │ ├── version/ # App version endpoint
│ │ └── client-error/ # Client error reporting
│ └── health/+server.ts # Health check
scripts/
└── patch-sdk.mjs # SDK postinstall patch
tests/
├── chat.spec.ts # Chat functionality tests
├── login.spec.ts # Login flow tests
├── messages.spec.ts # Message rendering tests
├── responsive.spec.ts # Mobile/desktop responsive tests
└── screenshots.spec.ts # Screenshot generation
infra/ # Azure Bicep IaC
├── main.bicep # Main template
├── main.parameters.json # Parameters
└── modules/ # Container Apps, ACR, Identity, Monitoring
See docs/ARCHITECTURE.md for detailed architecture, data flow, and component inventory.
This section documents the OAuth model, token scopes, and runtime authorization to help teams evaluate the app for security and governance.
This app uses a GitHub OAuth App (not a GitHub App). It authenticates via the Device Flow — the same flow used by the GitHub CLI. No client secret is needed; only the Client ID is required.
The app requests the following OAuth scopes when the user authenticates:
| Scope | Why it's needed | What it grants |
|---|---|---|
copilot |
Required by the Copilot SDK to call the Copilot API | Access to Copilot chat completions and model listing |
read:user |
Display the user's name and avatar in the UI | Read-only access to the user's GitHub profile |
repo |
Required by the SDK's built-in tools (file access, shell commands, code search) | Read and write access to all repositories the user can access |
[!IMPORTANT] The
reposcope is broad — it grants read/write access to the user's repositories. This is required because the Copilot SDK's built-in tools (file operations, shell, code search) need repository access to function. This matches the permissions of the desktop Copilot CLI. If your governance policy restricts this scope, consider usingALLOWED_GITHUB_USERSto limit who can authenticate, or useexcludedToolsin the session config to disable tools that require repo access.
| Usage | Detail |
|---|---|
| Copilot API | Passed to CopilotClient to authenticate against the Copilot API for chat completions and model listing |
| GitHub MCP tools | Passed as a Bearer token to the GitHub MCP server (/mcp/x/all) for built-in tools (issues, PRs, code search, repo management) |
| Identity validation | Used to call GET /user on the GitHub API to verify the user's identity and check token validity |
SESSION_SECRET), bridged to SvelteKit via hooks.server.ts. It is never sent to the browser.TOKEN_MAX_AGE_MS. On WebSocket connect, the token is also validated against GET /user to catch revoked tokens.| Control | Description |
|---|---|
| User allowlist | Set ALLOWED_GITHUB_USERS (comma-separated) to restrict login to specific GitHub accounts |
| Copilot license | A GitHub Copilot license (free, pro, or enterprise) is required — users without one cannot use the Copilot API |
| Session expiry | Sessions expire after TOKEN_MAX_AGE_MS (default: 24h), forcing re-authentication |
| Rate limiting | 200 requests per 15 minutes per IP address |
| IP restrictions | When deployed to Azure, IP allowlists can be configured via the ipRestrictions Bicep parameter |
azd upaz login && azd auth login
azd env set GITHUB_CLIENT_ID <your-client-id>
# Optional
azd env set allowedGithubUsers "user1,user2"
azd env set ipRestrictions "203.0.113.0/24"
azd up
Provisions: Container Registry, Container App (auto-TLS, CORS), Managed Identity, Log Analytics + App Insights. SESSION_SECRET auto-generated, all secrets encrypted at rest.
azd deploy # Subsequent deploys (skip infra provisioning)
Two workflows are included:
ci.yml — Runs on every push and PR: checkout → Node 24 → npm ci → npm audit → npm run check (type check) → npm run builddeploy.yml — Runs on push to master + manual dispatch: Azure login → ACR login → Docker build + push (SHA + latest tags) → Deploy to Azure Container Apps → Health checkCreate a service principal:
az ad sp create-for-rbac \
--name "copilot-unleashed-cicd" \
--role contributor \
--scopes /subscriptions/<sub-id>/resourceGroups/<rg> \
--sdk-auth
Add to GitHub → Settings → Secrets → Actions:
| Secret | Value |
|---|---|
AZURE_CREDENTIALS |
JSON output from above |
ACR_LOGIN_SERVER |
<registry>.azurecr.io |
ACR_NAME |
Registry name |
AZURE_RESOURCE_GROUP |
Resource group name |
See CONTRIBUTING.md for development setup, code style guide, and PR process.
MIT