Skip to content

Commit b011705

Browse files
committed
Release v0.0.15
1 parent 42f20ce commit b011705

32 files changed

+755
-286
lines changed

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ VITE_POSTHOG_HOST=https://us.i.posthog.com
2727
# Feedback URL (optional - defaults to open source Discord if not set)
2828
# Set this in hosted builds to use a private feedback channel
2929
# VITE_FEEDBACK_URL=https://discord.gg/your-private-invite
30+
31+
# API URL (optional - defaults to https://21st.dev)
32+
# Only change this if you're running the web app locally
33+
# MAIN_VITE_API_URL=http://localhost:3000

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# 1Code
22

3+
[1Code.dev](https://1code.dev)
4+
35
Best UI for Claude Code with local and remote agent execution.
46

5-
By [21st.dev](https://21st.dev)
7+
By [21st.dev](https://21st.dev) team
68

79
## Features
810

@@ -20,10 +22,13 @@ By [21st.dev](https://21st.dev)
2022
```bash
2123
# Prerequisites: Bun, Python, Xcode Command Line Tools (macOS)
2224
bun install
25+
bun run claude:download # Download Claude binary (required!)
2326
bun run build
2427
bun run package:mac # or package:win, package:linux
2528
```
2629

30+
> **Important:** The `claude:download` step downloads the Claude CLI binary which is required for the agent chat to work. If you skip this step, the app will build but agent functionality won't work.
31+
2732
### Option 2: Subscribe to 1code.dev (recommended)
2833

2934
Get pre-built releases + background agents support by subscribing at [1code.dev](https://1code.dev).
@@ -34,6 +39,7 @@ Your subscription helps us maintain and improve 1Code.
3439

3540
```bash
3641
bun install
42+
bun run claude:download # First time only
3743
bun run dev
3844
```
3945

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "21st-desktop",
3-
"version": "0.0.14",
3+
"version": "0.0.15",
44
"private": true,
55
"description": "1Code - UI for parallel work with AI agents",
66
"author": "21st.dev",
@@ -85,6 +85,7 @@
8585
"react-icons": "^5.5.0",
8686
"react-markdown": "^10.1.0",
8787
"react-syntax-highlighter": "^16.1.0",
88+
"remark-breaks": "^4.0.0",
8889
"remark-gfm": "^4.0.1",
8990
"shiki": "^1.24.4",
9091
"simple-git": "^3.28.0",

src/main/auth-manager.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { AuthStore, AuthData, AuthUser } from "./auth-store"
22
import { app, BrowserWindow } from "electron"
33

4-
// API URLs
5-
const API_URLS = {
6-
production: "https://21st.dev",
7-
development: "http://localhost:3000",
8-
} as const
4+
// Auth API URL - use env variable for local testing, default to production
5+
const AUTH_API_URL = import.meta.env.MAIN_VITE_API_URL || "https://21st.dev"
96

107
export class AuthManager {
118
private store: AuthStore
@@ -32,7 +29,7 @@ export class AuthManager {
3229
}
3330

3431
private getApiUrl(): string {
35-
return this.isDev ? API_URLS.development : API_URLS.production
32+
return AUTH_API_URL
3633
}
3734

3835
/**
@@ -218,4 +215,34 @@ export class AuthManager {
218215

219216
shell.openExternal(authUrl)
220217
}
218+
219+
/**
220+
* Update user profile on server and locally
221+
*/
222+
async updateUser(updates: { name?: string }): Promise<AuthUser | null> {
223+
const token = await this.getValidToken()
224+
if (!token) {
225+
throw new Error("Not authenticated")
226+
}
227+
228+
// Update on server using X-Desktop-Token header
229+
const response = await fetch(`${this.getApiUrl()}/api/user/profile`, {
230+
method: "PATCH",
231+
headers: {
232+
"Content-Type": "application/json",
233+
"X-Desktop-Token": token,
234+
},
235+
body: JSON.stringify({
236+
display_name: updates.name,
237+
}),
238+
})
239+
240+
if (!response.ok) {
241+
const error = await response.json().catch(() => ({ error: "Unknown error" }))
242+
throw new Error(error.error || `Update failed: ${response.status}`)
243+
}
244+
245+
// Update locally
246+
return this.store.updateUser({ name: updates.name ?? null })
247+
}
221248
}

src/main/auth-store.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,16 @@ export class AuthStore {
187187
const fiveMinutes = 5 * 60 * 1000
188188
return expiresAt - Date.now() < fiveMinutes
189189
}
190+
191+
/**
192+
* Update user data (e.g., after profile update)
193+
*/
194+
updateUser(updates: Partial<AuthUser>): AuthUser | null {
195+
const data = this.load()
196+
if (!data) return null
197+
198+
data.user = { ...data.user, ...updates }
199+
this.save(data)
200+
return data.user
201+
}
190202
}

src/main/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,9 @@ if (app.isPackaged && !IS_DEV) {
5656
}
5757

5858
// URL configuration (exported for use in other modules)
59+
// Uses MAIN_VITE_API_URL from .env if set, otherwise defaults to production
5960
export function getBaseUrl(): string {
60-
return process.env.ELECTRON_RENDERER_URL
61-
? "http://localhost:3000"
62-
: "https://21st.dev"
61+
return import.meta.env.MAIN_VITE_API_URL || "https://21st.dev"
6362
}
6463

6564
export function getAppUrl(): string {
@@ -487,10 +486,10 @@ if (gotTheLock) {
487486
label: "File",
488487
submenu: [
489488
{
490-
label: "New Agent",
489+
label: "New Chat",
491490
accelerator: "CmdOrCtrl+N",
492491
click: () => {
493-
console.log("[Menu] New Agent clicked (Cmd+N)")
492+
console.log("[Menu] New Chat clicked (Cmd+N)")
494493
const win = getWindow()
495494
if (win) {
496495
console.log("[Menu] Sending shortcut:new-agent to renderer")

src/main/lib/config.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,14 @@
22
* Shared configuration for the desktop app
33
*/
44

5-
// API URLs
6-
const API_URLS = {
7-
production: "https://21st.dev",
8-
development: "http://localhost:3000",
9-
} as const
10-
115
const IS_DEV = !!process.env.ELECTRON_RENDERER_URL
126

137
/**
14-
* Get the API base URL based on environment
8+
* Get the API base URL
9+
* Uses MAIN_VITE_API_URL from .env if set, otherwise defaults to production
1510
*/
1611
export function getApiUrl(): string {
17-
return IS_DEV ? API_URLS.development : API_URLS.production
12+
return import.meta.env.MAIN_VITE_API_URL || "https://21st.dev"
1813
}
1914

2015
/**

src/main/windows/main.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,16 @@ function registerIpcHandlers(getWindow: () => BrowserWindow | null): void {
179179
}
180180
await handleAuthCode(code)
181181
})
182+
183+
ipcMain.handle("auth:update-user", async (event, updates: { name?: string }) => {
184+
if (!validateSender(event)) return null
185+
try {
186+
return await getAuthManager().updateUser(updates)
187+
} catch (error) {
188+
console.error("[Auth] Failed to update user:", error)
189+
throw error
190+
}
191+
})
182192
}
183193

184194
// Current window reference

src/preload/index.d.ts

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,90 @@
1+
export interface UpdateInfo {
2+
version: string
3+
releaseDate?: string
4+
}
5+
6+
export interface UpdateProgress {
7+
percent: number
8+
bytesPerSecond: number
9+
transferred: number
10+
total: number
11+
}
12+
13+
export interface DesktopUser {
14+
id: string
15+
email: string
16+
name: string | null
17+
imageUrl: string | null
18+
username: string | null
19+
}
20+
121
export interface DesktopApi {
22+
// Platform info
23+
platform: NodeJS.Platform
24+
arch: string
225
getVersion: () => Promise<string>
3-
checkUpdate: () => Promise<{ version: string; downloadUrl: string } | null>
4-
getPlatform: () => Promise<NodeJS.Platform>
5-
getUser: () => Promise<{
6-
id: string
7-
email: string
8-
name: string | null
9-
imageUrl: string | null
10-
username: string | null
11-
} | null>
26+
27+
// Auto-update
28+
checkForUpdates: () => Promise<UpdateInfo | null>
29+
downloadUpdate: () => Promise<boolean>
30+
installUpdate: () => void
31+
onUpdateChecking: (callback: () => void) => () => void
32+
onUpdateAvailable: (callback: (info: UpdateInfo) => void) => () => void
33+
onUpdateNotAvailable: (callback: () => void) => () => void
34+
onUpdateProgress: (callback: (progress: UpdateProgress) => void) => () => void
35+
onUpdateDownloaded: (callback: (info: UpdateInfo) => void) => () => void
36+
onUpdateError: (callback: (error: string) => void) => () => void
37+
onUpdateManualCheck: (callback: () => void) => () => void
38+
39+
// Window controls
40+
windowMinimize: () => Promise<void>
41+
windowMaximize: () => Promise<void>
42+
windowClose: () => Promise<void>
43+
windowIsMaximized: () => Promise<boolean>
44+
windowToggleFullscreen: () => Promise<void>
45+
windowIsFullscreen: () => Promise<boolean>
46+
setTrafficLightVisibility: (visible: boolean) => Promise<void>
47+
onFullscreenChange: (callback: (isFullscreen: boolean) => void) => () => void
48+
onFocusChange: (callback: (isFocused: boolean) => void) => () => void
49+
50+
// Zoom
51+
zoomIn: () => Promise<void>
52+
zoomOut: () => Promise<void>
53+
zoomReset: () => Promise<void>
54+
getZoom: () => Promise<number>
55+
56+
// DevTools
57+
toggleDevTools: () => Promise<void>
58+
59+
// Analytics
60+
setAnalyticsOptOut: (optedOut: boolean) => Promise<void>
61+
62+
// Native features
63+
setBadge: (count: number | null) => Promise<void>
64+
showNotification: (options: { title: string; body: string }) => Promise<void>
65+
openExternal: (url: string) => Promise<void>
66+
getApiBaseUrl: () => Promise<string>
67+
68+
// Clipboard
69+
clipboardWrite: (text: string) => Promise<void>
70+
clipboardRead: () => Promise<string>
71+
72+
// Auth
73+
getUser: () => Promise<DesktopUser | null>
1274
isAuthenticated: () => Promise<boolean>
1375
logout: () => Promise<void>
1476
startAuthFlow: () => Promise<void>
77+
submitAuthCode: (code: string) => Promise<void>
78+
updateUser: (updates: { name?: string }) => Promise<DesktopUser | null>
1579
onAuthSuccess: (callback: (user: any) => void) => () => void
1680
onAuthError: (callback: (error: string) => void) => () => void
81+
82+
// Shortcuts
83+
onShortcutNewAgent: (callback: () => void) => () => void
1784
}
1885

1986
declare global {
2087
interface Window {
21-
desktopApi?: DesktopApi
88+
desktopApi: DesktopApi
2289
}
2390
}

src/preload/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ contextBridge.exposeInMainWorld("desktopApi", {
118118
logout: () => ipcRenderer.invoke("auth:logout"),
119119
startAuthFlow: () => ipcRenderer.invoke("auth:start-flow"),
120120
submitAuthCode: (code: string) => ipcRenderer.invoke("auth:submit-code", code),
121+
updateUser: (updates: { name?: string }) => ipcRenderer.invoke("auth:update-user", updates),
121122

122123
// Auth events
123124
onAuthSuccess: (callback: (user: any) => void) => {
@@ -201,6 +202,13 @@ export interface DesktopApi {
201202
logout: () => Promise<void>
202203
startAuthFlow: () => Promise<void>
203204
submitAuthCode: (code: string) => Promise<void>
205+
updateUser: (updates: { name?: string }) => Promise<{
206+
id: string
207+
email: string
208+
name: string | null
209+
imageUrl: string | null
210+
username: string | null
211+
} | null>
204212
onAuthSuccess: (callback: (user: any) => void) => () => void
205213
onAuthError: (callback: (error: string) => void) => () => void
206214
// Shortcuts

0 commit comments

Comments
 (0)