copilot-unleashed Svelte Themes

Copilot Unleashed

Copilot Hub — Self-hosted multi-model AI chat powered by the GitHub Copilot SDK. A modern alternative to ChatGPT, Claude, and Gemini.

Copilot Unleashed

Your AI powerhouse — every model, every device, your server. Self-hosted multi-model AI chat powered by the official GitHub Copilot SDK.

SvelteKit 5 Svelte 5 Node.js 24 TypeScript Vite 7 Copilot SDK MIT License

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.


Why This Exists

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

Use Cases

1. Review PRs from your phone, on the go

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.


2. Run autonomous agents while you do something else

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.


3. Compare models on hard problems — in the same UI

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.


4. Extended thinking for the hardest bugs

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.


5. Connect your own tools and APIs

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.


6. Deploy a shared AI assistant for your team

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.


7. Resume long-running investigations across devices

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.


Screenshots

Login — mobile   Chat — mobile

Login — desktop   Chat — desktop


Features

  • Real-time streaming — token-by-token over WebSocket
  • All Copilot models — GPT-4.1, o-series, Claude, Gemini — switch mid-conversation
  • Extended thinking — live reasoning traces with color-coded effort levels for supported models
  • Modes — ask / plan / autopilot, just like the CLI
  • GitHub MCP tools — issues, PRs, code search, repos — all built in, with readonly toggle
  • Custom MCP servers — add, update, and delete external MCP servers
  • File attachments — attach code files and images to messages
  • Permission control — approve/deny/always-allow tool execution per tool
  • Custom webhook tools — define your own tools backed by HTTP endpoints
  • Infinite sessions — automatic context compaction with configurable thresholds
  • Session management — list, resume, and delete past sessions
  • Custom instructions — appended to the system prompt, SDK guardrails preserved
  • Subagent support — agent selection, lifecycle tracking, and status display
  • Plan management — view, edit, and delete plans with collapsible panel
  • Mobile-first dark UI — SvelteKit 5 with SSR, responsive, touch-optimized
  • 2 env vars to runGITHUB_CLIENT_ID + SESSION_SECRET

Quick Start

Prerequisites

  • GitHub account with a Copilot license (free tier works)

  • GitHub OAuth Appregister one in 30 seconds:

    1. Click New OAuth App → name it anything, set both URLs to http://localhost:3000
    2. Copy the Client ID — that's your GITHUB_CLIENT_ID

    Device Flow auth — no client secret needed, no redirect URI to configure.

GitHub Codespaces (fastest)

  1. Add GITHUB_CLIENT_ID as a Codespace secret scoped to this repo

  2. Launch:

  3. Done — the app builds and opens automatically.

Docker

# 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.

Node.js

Requires Node.js 24+ (the SDK needs node:sqlite).

npm install && npm run build && npm start

Development

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)

Configuration

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)

Tech Stack

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)

How It Works

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
        ▼                               ▼                                    ▼
  1. User opens the app → SvelteKit SSR checks session → renders login or chat
  2. Authenticates via GitHub Device Flow, token stored server-side only
  3. WebSocket connection opens → server spawns a CopilotClient with its own CLI subprocess
  4. User sends a message → SDK streams events → server forwards as typed JSON → Svelte reactively re-renders
  5. On disconnect → session pooled with TTL, reconnect replays buffered messages

Full architecture docs →

SDK features implemented
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
WebSocket message protocol (21 client → 46+ server types)

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
Security
  • Server-side tokens — GitHub token in Express session, never sent to browser
  • CSP — Content Security Policy via SvelteKit hooks (self, unsafe-inline for Svelte, ws/wss, GitHub avatars)
  • CSRF protection — Origin header validation for state-changing requests in production
  • Rate limiting — 200 req / 15 min per IP (Map-based in hooks.server.ts, periodic cleanup)
  • Secure cookies — httpOnly, secure (prod), sameSite: lax, rolling, 7-day maxAge
  • Security headers — HSTS, X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy
  • Token freshness — configurable expiry + GitHub API revalidation on WebSocket connect
  • Origin validation — WebSocket origin checked against BASE_URL in production
  • User allowlist — optional ALLOWED_GITHUB_USERS
  • Input limits — 10,000 char messages, 2,000 char instructions (server-enforced)
  • Upload limits — 10MB per file, 5 files max, extension allowlist, path traversal prevention
  • XSS prevention — DOMPurify on all rendered markdown
  • SSRF prevention — Internal IP range blocklist for custom webhook tools
  • Permission hooks — Per-tool allow/deny with 30s auto-deny timeout
  • System prompt safety — append mode only, SDK guardrails preserved
  • Heartbeat — 30s ping/pong on WebSocket connections
Project structure
server.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.


Authentication & Authorization

This section documents the OAuth model, token scopes, and runtime authorization to help teams evaluate the app for security and governance.

OAuth App type

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.

Token scopes

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 repo scope 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 using ALLOWED_GITHUB_USERS to limit who can authenticate, or use excludedTools in the session config to disable tools that require repo access.

How the token is used

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

Token lifecycle

  1. Acquisition — User authenticates via Device Flow; token is returned by GitHub's OAuth endpoint
  2. Storage — Token is stored server-side only in the Express session (encrypted via SESSION_SECRET), bridged to SvelteKit via hooks.server.ts. It is never sent to the browser.
  3. Freshness — Token age is checked on every API request against TOKEN_MAX_AGE_MS. On WebSocket connect, the token is also validated against GET /user to catch revoked tokens.
  4. Revocation — When the user logs out or the session expires, the token is destroyed server-side. Users can also revoke the token from GitHub Settings → Applications.

Access control

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

What this app does NOT do

  • No client secret — Device Flow does not require or use a client secret
  • No server-side repo operations — The server itself never reads or writes repositories; repo access is used only by the Copilot SDK's built-in tools running within the user's own session
  • No token sharing — Each user's token is isolated in their own server-side session
  • No long-term storage — Tokens are held only in-memory (or file-based sessions in dev); nothing is persisted to a database

Deploy to Azure

Azure Container Apps with azd up
az 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)
CI/CD with GitHub Actions

Two workflows are included:

  • ci.yml — Runs on every push and PR: checkout → Node 24 → npm cinpm auditnpm run check (type check) → npm run build
  • deploy.yml — Runs on push to master + manual dispatch: Azure login → ACR login → Docker build + push (SHA + latest tags) → Deploy to Azure Container Apps → Health check

Create 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

Contributing

See CONTRIBUTING.md for development setup, code style guide, and PR process.

License

MIT

Top categories

Loading Svelte Themes