Skip to content

Commit 19132be

Browse files
committed
Release v0.0.32
## What's New ### Features - **MCP Authentication** — Connect to authenticated MCP servers with OAuth support - **Middle-click to close tabs** — Close terminal tabs with scroll wheel click ### Improvements & Fixes - Fixed plan approval triggering in all chats instead of just the current one - Fixed queue tips display - Web download link now automatically points to latest version
1 parent 79e03e7 commit 19132be

File tree

17 files changed

+3388
-522
lines changed

17 files changed

+3388
-522
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@ By [21st.dev](https://21st.dev) team
88

99
> **Platforms:** macOS, Linux, and Windows. Windows support improved thanks to community contributions from [@jesus-mgtc](https://github.com/jesus-mgtc) and [@evgyur](https://github.com/evgyur).
1010
11+
## 1Code vs Claude Code
12+
13+
| Feature | 1Code | Claude Code |
14+
|---------|-------|-------------|
15+
| **Visual UI** | ✅ Cursor-like desktop app ||
16+
| **Git Worktree Isolation** | ✅ Each chat runs in isolated worktree ||
17+
| **Background Execution** | ✅ Run multiple agents in parallel ||
18+
| **Built-in Git Client** | ✅ Visual staging, commits, branches | ❌ CLI git commands only |
19+
| **Integrated Terminal** |||
20+
| **Plan Mode** |||
21+
| **MCP Support** |||
22+
| **Memory (CLAUDE.md)** |||
23+
| **Skills & Slash Commands** |||
24+
| **Custom Subagents** |||
25+
| **Subscription & API Key Support** |||
26+
| **Custom Models & Providers (BYOK)** |||
27+
| **Checkpointing** | 🚧 Beta ||
28+
| **Tool Approve** | 📋 Backlog ||
29+
| **Hooks** |||
30+
1131
## Features
1232

1333
### Run Claude agents the right way

bun.lock

Lines changed: 272 additions & 143 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "21st-desktop",
3-
"version": "0.0.31",
3+
"version": "0.0.32",
44
"private": true,
55
"description": "1Code - UI for parallel work with AI agents",
66
"author": {
@@ -22,6 +22,7 @@
2222
"claude:download": "node scripts/download-claude-binary.mjs",
2323
"claude:download:all": "node scripts/download-claude-binary.mjs --all",
2424
"release": "rm -rf release && bun run claude:download && bun run build && bun run package:mac && bun run dist:manifest && ./scripts/upload-release-wrangler.sh",
25+
"release:dev": "rm -rf release && bun run claude:download && bun run build && bun run package:mac && rm -rf node_modules && bun i",
2526
"sync:public": "./scripts/sync-to-public.sh",
2627
"icon:generate": "node scripts/generate-icon.mjs",
2728
"db:generate": "drizzle-kit generate",
@@ -35,6 +36,7 @@
3536
"@anthropic-ai/claude-agent-sdk": "^0.2.12",
3637
"@git-diff-view/react": "^0.0.35",
3738
"@git-diff-view/shiki": "^0.0.36",
39+
"@modelcontextprotocol/sdk": "^1.25.3",
3840
"@radix-ui/react-accordion": "^1.2.12",
3941
"@radix-ui/react-alert-dialog": "^1.1.15",
4042
"@radix-ui/react-checkbox": "^1.3.3",

src/main/index.ts

Lines changed: 112 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import { app, BrowserWindow, session, Menu } from "electron"
2-
import { join } from "path"
3-
import { createServer } from "http"
4-
import { readFileSync, existsSync, unlinkSync, readlinkSync } from "fs"
51
import * as Sentry from "@sentry/electron/main"
6-
import { initDatabase, closeDatabase } from "./lib/db"
7-
import { createMainWindow, getWindow, showLoginPage } from "./windows/main"
2+
import { app, BrowserWindow, Menu, session } from "electron"
3+
import { existsSync, readFileSync, readlinkSync, unlinkSync } from "fs"
4+
import { createServer } from "http"
5+
import { join } from "path"
86
import { AuthManager } from "./auth-manager"
97
import {
10-
initAnalytics,
118
identify,
9+
initAnalytics,
10+
shutdown as shutdownAnalytics,
1211
trackAppOpened,
1312
trackAuthCompleted,
14-
shutdown as shutdownAnalytics,
1513
} from "./lib/analytics"
1614
import {
17-
initAutoUpdater,
1815
checkForUpdates,
1916
downloadUpdate,
17+
initAutoUpdater,
2018
setupFocusUpdateCheck,
2119
} from "./lib/auto-updater"
20+
import { closeDatabase, initDatabase } from "./lib/db"
2221
import { cleanupGitWatchers } from "./lib/git/watcher"
22+
import { cancelAllPendingOAuth, handleMcpOAuthCallback } from "./lib/mcp-auth"
23+
import { createMainWindow, getWindow } from "./windows/main"
2324

2425
// Dev mode detection
2526
const IS_DEV = !!process.env.ELECTRON_RENDERER_URL
@@ -134,14 +135,24 @@ function handleDeepLink(url: string): void {
134135
try {
135136
const parsed = new URL(url)
136137

137-
// Handle auth callback: twentyfirstdev://auth?code=xxx
138+
// Handle auth callback: twentyfirst-agents://auth?code=xxx
138139
if (parsed.pathname === "/auth" || parsed.host === "auth") {
139140
const code = parsed.searchParams.get("code")
140141
if (code) {
141142
handleAuthCode(code)
142143
return
143144
}
144145
}
146+
147+
// Handle MCP OAuth callback: twentyfirst-agents://mcp-oauth?code=xxx&state=yyy
148+
if (parsed.pathname === "/mcp-oauth" || parsed.host === "mcp-oauth") {
149+
const code = parsed.searchParams.get("code")
150+
const state = parsed.searchParams.get("state")
151+
if (code && state) {
152+
handleMcpOAuthCallback(code, state)
153+
return
154+
}
155+
}
145156
} catch (e) {
146157
console.error("[DeepLink] Failed to parse:", e)
147158
}
@@ -219,10 +230,9 @@ console.log("[Protocol] =============================================")
219230
const FAVICON_SVG = `<svg width="32" height="32" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="1024" height="1024" fill="#0033FF"/><path fill-rule="evenodd" clip-rule="evenodd" d="M800.165 148C842.048 148 876 181.952 876 223.835V686.415C876 690.606 872.606 694 868.415 694H640.915C636.729 694 633.335 697.394 633.335 701.585V868.415C633.335 872.606 629.936 876 625.75 876H223.835C181.952 876 148 842.048 148 800.165V702.59C148 697.262 150.807 692.326 155.376 689.586L427.843 526.1C434.031 522.388 431.956 513.238 425.327 512.118L423.962 512H155.585C151.394 512 148 508.606 148 504.415V337.585C148 333.394 151.394 330 155.585 330H443.75C447.936 330 451.335 326.606 451.335 322.415V155.585C451.335 151.394 454.729 148 458.915 148H800.165ZM458.915 330C454.729 330 451.335 333.394 451.335 337.585V686.415C451.335 690.606 454.729 694 458.915 694H625.75C629.936 694 633.335 690.606 633.335 686.415V337.585C633.335 333.394 629.936 330 625.75 330H458.915Z" fill="#F4F4F4"/></svg>`
220231
const FAVICON_DATA_URI = `data:image/svg+xml,${encodeURIComponent(FAVICON_SVG)}`
221232

222-
// Dev mode: Start local HTTP server for auth callback
223-
// This catches http://localhost:21321/auth/callback?code=xxx
224-
if (process.env.ELECTRON_RENDERER_URL) {
225-
const server = createServer((req, res) => {
233+
// Start local HTTP server for auth callbacks
234+
// This catches http://localhost:21321/auth/callback?code=xxx and /mcp-oauth/callback
235+
const server = createServer((req, res) => {
226236
const url = new URL(req.url || "", "http://localhost:21321")
227237

228238
// Serve favicon
@@ -312,16 +322,99 @@ if (process.env.ELECTRON_RENDERER_URL) {
312322
res.writeHead(400, { "Content-Type": "text/plain" })
313323
res.end("Missing code parameter")
314324
}
325+
} else if (url.pathname === "/mcp-oauth/callback") {
326+
// Handle MCP OAuth callback in dev mode
327+
const code = url.searchParams.get("code")
328+
const state = url.searchParams.get("state")
329+
console.log(
330+
"[Auth Server] Received MCP OAuth callback with code:",
331+
code?.slice(0, 8) + "...",
332+
"state:",
333+
state?.slice(0, 8) + "...",
334+
)
335+
336+
if (code && state) {
337+
// Handle the MCP OAuth callback
338+
handleMcpOAuthCallback(code, state)
339+
340+
// Send success response and close the browser tab
341+
res.writeHead(200, { "Content-Type": "text/html" })
342+
res.end(`<!DOCTYPE html>
343+
<html>
344+
<head>
345+
<meta charset="UTF-8">
346+
<link rel="icon" type="image/svg+xml" href="${FAVICON_DATA_URI}">
347+
<title>1Code - MCP Authentication</title>
348+
<style>
349+
* { margin: 0; padding: 0; box-sizing: border-box; }
350+
:root {
351+
--bg: #09090b;
352+
--text: #fafafa;
353+
--text-muted: #71717a;
354+
}
355+
@media (prefers-color-scheme: light) {
356+
:root {
357+
--bg: #ffffff;
358+
--text: #09090b;
359+
--text-muted: #71717a;
360+
}
361+
}
362+
body {
363+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
364+
display: flex;
365+
flex-direction: column;
366+
align-items: center;
367+
justify-content: center;
368+
min-height: 100vh;
369+
background: var(--bg);
370+
color: var(--text);
371+
}
372+
.container {
373+
display: flex;
374+
flex-direction: column;
375+
align-items: center;
376+
gap: 8px;
377+
}
378+
.logo {
379+
width: 24px;
380+
height: 24px;
381+
margin-bottom: 8px;
382+
}
383+
h1 {
384+
font-size: 14px;
385+
font-weight: 500;
386+
margin-bottom: 4px;
387+
}
388+
p {
389+
font-size: 12px;
390+
color: var(--text-muted);
391+
}
392+
</style>
393+
</head>
394+
<body>
395+
<div class="container">
396+
<svg class="logo" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
397+
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.3333 0C15.2538 0 16 0.746192 16 1.66667V11.8333C16 11.9254 15.9254 12 15.8333 12H10.8333C10.7413 12 10.6667 12.0746 10.6667 12.1667V15.8333C10.6667 15.9254 10.592 16 10.5 16H1.66667C0.746192 16 0 15.2538 0 14.3333V12.1888C0 12.0717 0.0617409 11.9632 0.162081 11.903L6.15043 8.30986C6.28644 8.22833 6.24077 8.02716 6.09507 8.00256L6.06511 8H0.166667C0.0746186 8 0 7.92538 0 7.83333V4.16667C0 4.07462 0.0746193 4 0.166667 4H6.5C6.59205 4 6.66667 3.92538 6.66667 3.83333V0.166667C6.66667 0.0746193 6.74129 0 6.83333 0H14.3333ZM6.83333 4C6.74129 4 6.66667 4.07462 6.66667 4.16667V11.8333C6.66667 11.9254 6.74129 12 6.83333 12H10.5C10.592 12 10.6667 11.9254 10.6667 11.8333V4.16667C10.6667 4.07462 10.592 4 10.5 4H6.83333Z" fill="#0033FF"/>
398+
</svg>
399+
<h1>MCP Server authenticated</h1>
400+
<p>You can close this tab</p>
401+
</div>
402+
<script>setTimeout(() => window.close(), 1000)</script>
403+
</body>
404+
</html>`)
405+
} else {
406+
res.writeHead(400, { "Content-Type": "text/plain" })
407+
res.end("Missing code or state parameter")
408+
}
315409
} else {
316410
res.writeHead(404, { "Content-Type": "text/plain" })
317411
res.end("Not found")
318412
}
319413
})
320414

321-
server.listen(21321, () => {
322-
console.log("[Auth Server] Listening on http://localhost:21321")
323-
})
324-
}
415+
server.listen(21321, () => {
416+
console.log("[Auth Server] Listening on http://localhost:21321")
417+
})
325418

326419
// Clean up stale lock files from crashed instances
327420
// Returns true if locks were cleaned, false otherwise
@@ -670,6 +763,7 @@ if (gotTheLock) {
670763
// Cleanup before quit
671764
app.on("before-quit", async () => {
672765
console.log("[App] Shutting down...")
766+
cancelAllPendingOAuth()
673767
await cleanupGitWatchers()
674768
await shutdownAnalytics()
675769
await closeDatabase()

0 commit comments

Comments
 (0)