diff --git a/README.ar.md b/README.ar.md
index edac204a28..f24e598d5e 100644
--- a/README.ar.md
+++ b/README.ar.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.br.md b/README.br.md
index c185603efb..4802c4996f 100644
--- a/README.br.md
+++ b/README.br.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.bs.md b/README.bs.md
index d64a69c0d7..9ad6852018 100644
--- a/README.bs.md
+++ b/README.bs.md
@@ -32,7 +32,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.da.md b/README.da.md
index 7f3d5aa5dd..4b1302dbc3 100644
--- a/README.da.md
+++ b/README.da.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.de.md b/README.de.md
index 2aa78657ca..16116dc72f 100644
--- a/README.de.md
+++ b/README.de.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.es.md b/README.es.md
index 2b80427ab2..5c18ff4aca 100644
--- a/README.es.md
+++ b/README.es.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.fr.md b/README.fr.md
index bc3fe9e757..0382164bed 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.it.md b/README.it.md
index 6da7d51fc6..c966ccec49 100644
--- a/README.it.md
+++ b/README.it.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.ja.md b/README.ja.md
index 7a0bbb08f3..11109e7eb4 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.ko.md b/README.ko.md
index 1c931c31f3..23fea76b1e 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.md b/README.md
index bd01fc94e8..99b4b2c50f 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.no.md b/README.no.md
index 092316bae1..9b9e90dc38 100644
--- a/README.no.md
+++ b/README.no.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.pl.md b/README.pl.md
index a225d82539..fced98dfc3 100644
--- a/README.pl.md
+++ b/README.pl.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.ru.md b/README.ru.md
index c13f039d16..a7c590c16b 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.th.md b/README.th.md
index ba2db8a850..0999167f23 100644
--- a/README.th.md
+++ b/README.th.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.tr.md b/README.tr.md
index 635a5782fe..67f84e4ddb 100644
--- a/README.tr.md
+++ b/README.tr.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.uk.md b/README.uk.md
new file mode 100644
index 0000000000..77e859a45d
--- /dev/null
+++ b/README.uk.md
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+AI-агент для програмування з відкритим кодом.
+
+
+
+
+
+
+
+ English |
+ 简体中文 |
+ 繁體中文 |
+ 한국어 |
+ Deutsch |
+ Español |
+ Français |
+ Italiano |
+ Dansk |
+ 日本語 |
+ Polski |
+ Русский |
+ Bosanski |
+ العربية |
+ Norsk |
+ Português (Brasil) |
+ ไทย |
+ Türkçe |
+ Українська
+
+
+[](https://opencode.ai)
+
+---
+
+### Встановлення
+
+```bash
+# YOLO
+curl -fsSL https://opencode.ai/install | bash
+
+# Менеджери пакетів
+npm i -g opencode-ai@latest # або bun/pnpm/yarn
+scoop install opencode # Windows
+choco install opencode # Windows
+brew install anomalyco/tap/opencode # macOS і Linux (рекомендовано, завжди актуально)
+brew install opencode # macOS і Linux (офіційна формула Homebrew, оновлюється рідше)
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
+mise use -g opencode # Будь-яка ОС
+nix run nixpkgs#opencode # або github:anomalyco/opencode для найновішої dev-гілки
+```
+
+> [!TIP]
+> Перед встановленням видаліть версії старші за 0.1.x.
+
+### Десктопний застосунок (BETA)
+
+OpenCode також доступний як десктопний застосунок. Завантажуйте напряму зі [сторінки релізів](https://github.com/anomalyco/opencode/releases) або [opencode.ai/download](https://opencode.ai/download).
+
+| Платформа | Завантаження |
+| --------------------- | ------------------------------------- |
+| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
+| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
+| Windows | `opencode-desktop-windows-x64.exe` |
+| Linux | `.deb`, `.rpm` або AppImage |
+
+```bash
+# macOS (Homebrew)
+brew install --cask opencode-desktop
+# Windows (Scoop)
+scoop bucket add extras; scoop install extras/opencode-desktop
+```
+
+#### Каталог встановлення
+
+Скрипт встановлення дотримується такого порядку пріоритету для шляху встановлення:
+
+1. `$OPENCODE_INSTALL_DIR` - Користувацький каталог встановлення
+2. `$XDG_BIN_DIR` - Шлях, сумісний зі специфікацією XDG Base Directory
+3. `$HOME/bin` - Стандартний каталог користувацьких бінарників (якщо існує або його можна створити)
+4. `$HOME/.opencode/bin` - Резервний варіант за замовчуванням
+
+```bash
+# Приклади
+OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
+XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
+```
+
+### Агенти
+
+OpenCode містить два вбудовані агенти, між якими можна перемикатися клавішею `Tab`.
+
+- **build** - Агент за замовчуванням із повним доступом для завдань розробки
+- **plan** - Агент лише для читання для аналізу та дослідження коду
+ - За замовчуванням забороняє редагування файлів
+ - Запитує дозвіл перед запуском bash-команд
+ - Ідеально підходить для дослідження незнайомих кодових баз або планування змін
+
+Також доступний допоміжний агент **general** для складного пошуку та багатокрокових завдань.
+Він використовується всередині системи й може бути викликаний у повідомленнях через `@general`.
+
+Дізнайтеся більше про [agents](https://opencode.ai/docs/agents).
+
+### Документація
+
+Щоб дізнатися більше про налаштування OpenCode, [**перейдіть до нашої документації**](https://opencode.ai/docs).
+
+### Внесок
+
+Якщо ви хочете зробити внесок в OpenCode, будь ласка, прочитайте нашу [документацію для контриб'юторів](./CONTRIBUTING.md) перед надсиланням pull request.
+
+### Проєкти на базі OpenCode
+
+Якщо ви працюєте над проєктом, пов'язаним з OpenCode, і використовуєте "opencode" у назві, наприклад "opencode-dashboard" або "opencode-mobile", додайте примітку до свого README.
+Уточніть, що цей проєкт не створений командою OpenCode і жодним чином не афілійований із нами.
+
+### FAQ
+
+#### Чим це відрізняється від Claude Code?
+
+За можливостями це дуже схоже на Claude Code. Ось ключові відмінності:
+
+- 100% open source
+- Немає прив'язки до конкретного провайдера. Ми рекомендуємо моделі, які надаємо через [OpenCode Zen](https://opencode.ai/zen), але OpenCode також працює з Claude, OpenAI, Google і навіть локальними моделями. З розвитком моделей різниця між ними зменшуватиметься, а ціни падатимуть, тому незалежність від провайдера має значення.
+- Підтримка LSP з коробки
+- Фокус на TUI. OpenCode створено користувачами neovim та авторами [terminal.shop](https://terminal.shop); ми й надалі розширюватимемо межі можливого в терміналі.
+- Клієнт-серверна архітектура. Наприклад, це дає змогу запускати OpenCode на вашому комп'ютері й керувати ним віддалено з мобільного застосунку, тобто TUI-фронтенд - лише один із можливих клієнтів.
+
+---
+
+**Приєднуйтеся до нашої спільноти** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
diff --git a/README.zh.md b/README.zh.md
index b2f288f5ba..113d476b2e 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/README.zht.md b/README.zht.md
index be4ef053c0..b518104443 100644
--- a/README.zht.md
+++ b/README.zht.md
@@ -31,7 +31,8 @@
Norsk |
Português (Brasil) |
ไทย |
- Türkçe
+ Türkçe |
+ Українська
[](https://opencode.ai)
diff --git a/bun.lock b/bun.lock
index c29176596d..59106e14af 100644
--- a/bun.lock
+++ b/bun.lock
@@ -23,7 +23,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -73,7 +73,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -107,7 +107,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -134,7 +134,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -158,7 +158,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -182,7 +182,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -215,7 +215,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -244,7 +244,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -260,7 +260,7 @@
},
"packages/opencode": {
"name": "opencode",
- "version": "1.2.2",
+ "version": "1.2.4",
"bin": {
"opencode": "./bin/opencode",
},
@@ -369,7 +369,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -389,7 +389,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.2.2",
+ "version": "1.2.4",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -400,7 +400,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -413,7 +413,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -455,7 +455,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"zod": "catalog:",
},
@@ -466,7 +466,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts
index 52c9007ea1..1a0afbab10 100644
--- a/packages/app/e2e/selectors.ts
+++ b/packages/app/e2e/selectors.ts
@@ -30,6 +30,9 @@ export const projectMenuTriggerSelector = (slug: string) =>
export const projectCloseMenuSelector = (slug: string) => `[data-action="project-close-menu"][data-project="${slug}"]`
+export const projectClearNotificationsSelector = (slug: string) =>
+ `[data-action="project-clear-notifications"][data-project="${slug}"]`
+
export const projectWorkspacesToggleSelector = (slug: string) =>
`[data-action="project-workspaces-toggle"][data-project="${slug}"]`
diff --git a/packages/app/package.json b/packages/app/package.json
index b2f2f23246..31afda6566 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
- "version": "1.2.2",
+ "version": "1.2.4",
"description": "",
"type": "module",
"exports": {
diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx
index 5552cc90b8..758f5a83f5 100644
--- a/packages/app/src/components/file-tree.tsx
+++ b/packages/app/src/components/file-tree.tsx
@@ -21,6 +21,8 @@ import {
import { Dynamic } from "solid-js/web"
import type { FileNode } from "@opencode-ai/sdk/v2"
+const MAX_DEPTH = 128
+
function pathToFileUrl(filepath: string): string {
return `file://${encodeFilePath(filepath)}`
}
@@ -260,12 +262,20 @@ export default function FileTree(props: {
_marks?: Set
_deeps?: Map
_kinds?: ReadonlyMap
+ _chain?: readonly string[]
}) {
const file = useFile()
const level = props.level ?? 0
const draggable = () => props.draggable ?? true
const tooltip = () => props.tooltip ?? true
+ const key = (p: string) =>
+ file
+ .normalize(p)
+ .replace(/[\\/]+$/, "")
+ .replaceAll("\\", "/")
+ const chain = props._chain ? [...props._chain, key(props.path)] : [key(props.path)]
+
const filter = createMemo(() => {
if (props._filter) return props._filter
@@ -307,23 +317,45 @@ export default function FileTree(props: {
const out = new Map()
- const visit = (dir: string, lvl: number): number => {
- const expanded = file.tree.state(dir)?.expanded ?? false
- if (!expanded) return -1
+ const root = props.path
+ if (!(file.tree.state(root)?.expanded ?? false)) return out
- const nodes = file.tree.children(dir)
- const max = nodes.reduce((max, node) => {
- if (node.type !== "directory") return max
- const open = file.tree.state(node.path)?.expanded ?? false
- if (!open) return max
- return Math.max(max, visit(node.path, lvl + 1))
- }, lvl)
+ const seen = new Set()
+ const stack: { dir: string; lvl: number; i: number; kids: string[]; max: number }[] = []
- out.set(dir, max)
- return max
+ const push = (dir: string, lvl: number) => {
+ const id = key(dir)
+ if (seen.has(id)) return
+ seen.add(id)
+
+ const kids = file.tree
+ .children(dir)
+ .filter((node) => node.type === "directory" && (file.tree.state(node.path)?.expanded ?? false))
+ .map((node) => node.path)
+
+ stack.push({ dir, lvl, i: 0, kids, max: lvl })
+ }
+
+ push(root, level - 1)
+
+ while (stack.length > 0) {
+ const top = stack[stack.length - 1]!
+
+ if (top.i < top.kids.length) {
+ const next = top.kids[top.i]!
+ top.i++
+ push(next, top.lvl + 1)
+ continue
+ }
+
+ out.set(top.dir, top.max)
+ stack.pop()
+
+ const parent = stack[stack.length - 1]
+ if (!parent) continue
+ parent.max = Math.max(parent.max, top.max)
}
- visit(props.path, level - 1)
return out
})
@@ -459,21 +491,27 @@ export default function FileTree(props: {
}}
style={`left: ${Math.max(0, 8 + level * 12 - 4) + 8}px`}
/>
-
+ ...}
+ >
+
+
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index 8e8c3c895b..984888c35d 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -911,31 +911,13 @@ export const PromptInput: Component = (props) => {
if (!collapsed) return
const cursorPosition = getCursorPosition(editorRef)
- const textLength = promptLength(prompt.current())
const textContent = prompt
.current()
.map((part) => ("content" in part ? part.content : ""))
.join("")
const direction = event.key === "ArrowUp" ? "up" : "down"
- if (!canNavigateHistoryAtCursor(direction, textContent, cursorPosition)) return
- const isEmpty = textContent.trim() === "" || textLength <= 1
- const hasNewlines = textContent.includes("\n")
- const inHistory = store.historyIndex >= 0
- const atStart = cursorPosition <= (isEmpty ? 1 : 0)
- const atEnd = cursorPosition >= (isEmpty ? textLength - 1 : textLength)
- const allowUp = isEmpty || atStart || (!hasNewlines && !inHistory) || (inHistory && atEnd)
- const allowDown = isEmpty || atEnd || (!hasNewlines && !inHistory) || (inHistory && atStart)
-
- if (direction === "up") {
- if (!allowUp) return
- if (navigateHistory("up")) {
- event.preventDefault()
- }
- return
- }
-
- if (!allowDown) return
- if (navigateHistory("down")) {
+ if (!canNavigateHistoryAtCursor(direction, textContent, cursorPosition, store.historyIndex >= 0)) return
+ if (navigateHistory(direction)) {
event.preventDefault()
}
return
diff --git a/packages/app/src/components/prompt-input/history.test.ts b/packages/app/src/components/prompt-input/history.test.ts
index a37fdad677..b7a4f896b8 100644
--- a/packages/app/src/components/prompt-input/history.test.ts
+++ b/packages/app/src/components/prompt-input/history.test.ts
@@ -73,7 +73,7 @@ describe("prompt-input history", () => {
expect(original[1].selection?.startLine).toBe(1)
})
- test("canNavigateHistoryAtCursor only allows multiline boundaries", () => {
+ test("canNavigateHistoryAtCursor only allows prompt boundaries", () => {
const value = "a\nb\nc"
expect(canNavigateHistoryAtCursor("up", value, 0)).toBe(true)
@@ -85,7 +85,16 @@ describe("prompt-input history", () => {
expect(canNavigateHistoryAtCursor("up", value, 5)).toBe(false)
expect(canNavigateHistoryAtCursor("down", value, 5)).toBe(true)
- expect(canNavigateHistoryAtCursor("up", "abc", 1)).toBe(true)
- expect(canNavigateHistoryAtCursor("down", "abc", 1)).toBe(true)
+ expect(canNavigateHistoryAtCursor("up", "abc", 0)).toBe(true)
+ expect(canNavigateHistoryAtCursor("down", "abc", 3)).toBe(true)
+ expect(canNavigateHistoryAtCursor("up", "abc", 1)).toBe(false)
+ expect(canNavigateHistoryAtCursor("down", "abc", 1)).toBe(false)
+
+ expect(canNavigateHistoryAtCursor("up", "abc", 0, true)).toBe(true)
+ expect(canNavigateHistoryAtCursor("up", "abc", 3, true)).toBe(true)
+ expect(canNavigateHistoryAtCursor("down", "abc", 0, true)).toBe(true)
+ expect(canNavigateHistoryAtCursor("down", "abc", 3, true)).toBe(true)
+ expect(canNavigateHistoryAtCursor("up", "abc", 1, true)).toBe(false)
+ expect(canNavigateHistoryAtCursor("down", "abc", 1, true)).toBe(false)
})
})
diff --git a/packages/app/src/components/prompt-input/history.ts b/packages/app/src/components/prompt-input/history.ts
index f26f808487..c279a3ed56 100644
--- a/packages/app/src/components/prompt-input/history.ts
+++ b/packages/app/src/components/prompt-input/history.ts
@@ -4,11 +4,13 @@ const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
export const MAX_HISTORY = 100
-export function canNavigateHistoryAtCursor(direction: "up" | "down", text: string, cursor: number) {
- if (!text.includes("\n")) return true
+export function canNavigateHistoryAtCursor(direction: "up" | "down", text: string, cursor: number, inHistory = false) {
const position = Math.max(0, Math.min(cursor, text.length))
- if (direction === "up") return !text.slice(0, position).includes("\n")
- return !text.slice(position).includes("\n")
+ const atStart = position === 0
+ const atEnd = position === text.length
+ if (inHistory) return atStart || atEnd
+ if (direction === "up") return position === 0
+ return position === text.length
}
export function clonePromptParts(prompt: Prompt): Prompt {
diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx
index 439f542bb1..d5a0b813b6 100644
--- a/packages/app/src/components/settings-general.tsx
+++ b/packages/app/src/components/settings-general.tsx
@@ -128,6 +128,7 @@ export const SettingsGeneral: Component = () => {
{ value: "roboto-mono", label: "font.option.robotoMono" },
{ value: "source-code-pro", label: "font.option.sourceCodePro" },
{ value: "ubuntu-mono", label: "font.option.ubuntuMono" },
+ { value: "geist-mono", label: "font.option.geistMono" },
] as const
const fontOptionsList = [...fontOptions]
diff --git a/packages/app/src/context/command.tsx b/packages/app/src/context/command.tsx
index 237d718846..03437c9735 100644
--- a/packages/app/src/context/command.tsx
+++ b/packages/app/src/context/command.tsx
@@ -316,8 +316,10 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const isPalette = palette().has(sig)
const option = keymap().get(sig)
const modified = event.ctrlKey || event.metaKey || event.altKey
+ const isTab = event.key === "Tab"
- if (isEditableTarget(event.target) && !isPalette && !isAllowedEditableKeybind(option?.id) && !modified) return
+ if (isEditableTarget(event.target) && !isPalette && !isAllowedEditableKeybind(option?.id) && !modified && !isTab)
+ return
if (isPalette) {
event.preventDefault()
diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx
index d72d4ceb1e..fbcd0a8518 100644
--- a/packages/app/src/context/settings.tsx
+++ b/packages/app/src/context/settings.tsx
@@ -85,6 +85,7 @@ const monoFonts: Record = {
"roboto-mono": `"Roboto Mono Nerd Font", "RobotoMono Nerd Font", "RobotoMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"source-code-pro": `"Source Code Pro Nerd Font", "SauceCodePro Nerd Font", "SauceCodePro Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
"ubuntu-mono": `"Ubuntu Mono Nerd Font", "UbuntuMono Nerd Font", "UbuntuMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
+ "geist-mono": `"GeistMono Nerd Font", "GeistMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
}
export function monoFontFamily(font: string | undefined) {
diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts
index e3792a3c3c..3d347c8423 100644
--- a/packages/app/src/i18n/ar.ts
+++ b/packages/app/src/i18n/ar.ts
@@ -509,6 +509,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "قم بتوصيل أي موفر لاستخدام النماذج، بما في ذلك Claude و GPT و Gemini وما إلى ذلك.",
"sidebar.project.recentSessions": "الجلسات الحديثة",
"sidebar.project.viewAllSessions": "عرض جميع الجلسات",
+ "sidebar.project.clearNotifications": "مسح الإشعارات",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "سطح المكتب",
"settings.section.server": "الخادم",
@@ -556,6 +557,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "تنبيه 01",
"sound.option.alert02": "تنبيه 02",
"sound.option.alert03": "تنبيه 03",
diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts
index 07d6ce467a..730c01fdff 100644
--- a/packages/app/src/i18n/br.ts
+++ b/packages/app/src/i18n/br.ts
@@ -515,6 +515,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Conecte qualquer provedor para usar modelos, incluindo Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sessões recentes",
"sidebar.project.viewAllSessions": "Ver todas as sessões",
+ "sidebar.project.clearNotifications": "Limpar notificações",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop",
"settings.section.server": "Servidor",
@@ -562,6 +563,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Alerta 01",
"sound.option.alert02": "Alerta 02",
"sound.option.alert03": "Alerta 03",
diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts
index 7d10da6ed8..d53c261126 100644
--- a/packages/app/src/i18n/bs.ts
+++ b/packages/app/src/i18n/bs.ts
@@ -576,6 +576,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Poveži bilo kojeg provajdera da koristiš modele, npr. Claude, GPT, Gemini itd.",
"sidebar.project.recentSessions": "Nedavne sesije",
"sidebar.project.viewAllSessions": "Prikaži sve sesije",
+ "sidebar.project.clearNotifications": "Očisti obavijesti",
"app.name.desktop": "OpenCode Desktop",
@@ -630,6 +631,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Upozorenje 01",
"sound.option.alert02": "Upozorenje 02",
"sound.option.alert03": "Upozorenje 03",
diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts
index ac5c4d494b..9faa14d3da 100644
--- a/packages/app/src/i18n/da.ts
+++ b/packages/app/src/i18n/da.ts
@@ -572,6 +572,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Forbind enhver udbyder for at bruge modeller, inkl. Claude, GPT, Gemini osv.",
"sidebar.project.recentSessions": "Seneste sessioner",
"sidebar.project.viewAllSessions": "Vis alle sessioner",
+ "sidebar.project.clearNotifications": "Ryd notifikationer",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop",
@@ -626,6 +627,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Alarm 01",
"sound.option.alert02": "Alarm 02",
"sound.option.alert03": "Alarm 03",
diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts
index 99a9506310..d350af6cf5 100644
--- a/packages/app/src/i18n/de.ts
+++ b/packages/app/src/i18n/de.ts
@@ -524,6 +524,7 @@ export const dict = {
"Verbinden Sie einen beliebigen Anbieter, um Modelle wie Claude, GPT, Gemini usw. zu nutzen.",
"sidebar.project.recentSessions": "Letzte Sitzungen",
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
+ "sidebar.project.clearNotifications": "Benachrichtigungen löschen",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop",
"settings.section.server": "Server",
@@ -571,6 +572,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Alarm 01",
"sound.option.alert02": "Alarm 02",
"sound.option.alert03": "Alarm 03",
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index 99513edaa1..cb42b016f1 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -577,6 +577,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Connect any provider to use models, inc. Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Recent sessions",
"sidebar.project.viewAllSessions": "View all sessions",
+ "sidebar.project.clearNotifications": "Clear notifications",
"app.name.desktop": "OpenCode Desktop",
@@ -631,6 +632,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts
index 7a6c4974e0..c4ec378dcd 100644
--- a/packages/app/src/i18n/es.ts
+++ b/packages/app/src/i18n/es.ts
@@ -579,6 +579,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Conecta cualquier proveedor para usar modelos, inc. Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sesiones recientes",
"sidebar.project.viewAllSessions": "Ver todas las sesiones",
+ "sidebar.project.clearNotifications": "Borrar notificaciones",
"app.name.desktop": "OpenCode Desktop",
@@ -634,6 +635,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Alerta 01",
"sound.option.alert02": "Alerta 02",
"sound.option.alert03": "Alerta 03",
diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts
index fc3bf26679..7069fbd98f 100644
--- a/packages/app/src/i18n/fr.ts
+++ b/packages/app/src/i18n/fr.ts
@@ -523,6 +523,7 @@ export const dict = {
"Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sessions récentes",
"sidebar.project.viewAllSessions": "Voir toutes les sessions",
+ "sidebar.project.clearNotifications": "Effacer les notifications",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Bureau",
"settings.section.server": "Serveur",
@@ -570,6 +571,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Alerte 01",
"sound.option.alert02": "Alerte 02",
"sound.option.alert03": "Alerte 03",
diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts
index b597db02a5..e7e24a9bd6 100644
--- a/packages/app/src/i18n/ja.ts
+++ b/packages/app/src/i18n/ja.ts
@@ -513,6 +513,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "プロバイダーを接続して、Claude、GPT、Geminiなどのモデルを使用できます。",
"sidebar.project.recentSessions": "最近のセッション",
"sidebar.project.viewAllSessions": "すべてのセッションを表示",
+ "sidebar.project.clearNotifications": "通知をクリア",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "デスクトップ",
"settings.section.server": "サーバー",
@@ -560,6 +561,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "アラート 01",
"sound.option.alert02": "アラート 02",
"sound.option.alert03": "アラート 03",
diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts
index 525bd03565..650b7e662a 100644
--- a/packages/app/src/i18n/ko.ts
+++ b/packages/app/src/i18n/ko.ts
@@ -514,6 +514,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Claude, GPT, Gemini 등을 포함한 모델을 사용하려면 공급자를 연결하세요.",
"sidebar.project.recentSessions": "최근 세션",
"sidebar.project.viewAllSessions": "모든 세션 보기",
+ "sidebar.project.clearNotifications": "알림 지우기",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "데스크톱",
"settings.section.server": "서버",
@@ -561,6 +562,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "알림 01",
"sound.option.alert02": "알림 02",
"sound.option.alert03": "알림 03",
diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts
index 98e79e1896..afc162ab17 100644
--- a/packages/app/src/i18n/no.ts
+++ b/packages/app/src/i18n/no.ts
@@ -579,6 +579,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Koble til en leverandør for å bruke modeller, inkl. Claude, GPT, Gemini osv.",
"sidebar.project.recentSessions": "Nylige sesjoner",
"sidebar.project.viewAllSessions": "Vis alle sesjoner",
+ "sidebar.project.clearNotifications": "Fjern varsler",
"app.name.desktop": "OpenCode Desktop",
@@ -633,6 +634,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Varsel 01",
"sound.option.alert02": "Varsel 02",
"sound.option.alert03": "Varsel 03",
diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts
index 983c9c14ac..d8572148a8 100644
--- a/packages/app/src/i18n/pl.ts
+++ b/packages/app/src/i18n/pl.ts
@@ -514,6 +514,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Połącz dowolnego dostawcę, aby używać modeli, w tym Claude, GPT, Gemini itp.",
"sidebar.project.recentSessions": "Ostatnie sesje",
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
+ "sidebar.project.clearNotifications": "Wyczyść powiadomienia",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Pulpit",
"settings.section.server": "Serwer",
@@ -561,6 +562,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts
index f2c87fe0f1..86d201cebc 100644
--- a/packages/app/src/i18n/ru.ts
+++ b/packages/app/src/i18n/ru.ts
@@ -578,6 +578,7 @@ export const dict = {
"Подключите любого провайдера для использования моделей, включая Claude, GPT, Gemini и др.",
"sidebar.project.recentSessions": "Недавние сессии",
"sidebar.project.viewAllSessions": "Посмотреть все сессии",
+ "sidebar.project.clearNotifications": "Очистить уведомления",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Приложение",
@@ -631,6 +632,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index 689e821189..83020bf8c0 100644
--- a/packages/app/src/i18n/th.ts
+++ b/packages/app/src/i18n/th.ts
@@ -571,6 +571,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "เชื่อมต่อผู้ให้บริการใด ๆ เพื่อใช้โมเดล รวมถึง Claude, GPT, Gemini ฯลฯ",
"sidebar.project.recentSessions": "เซสชันล่าสุด",
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
+ "sidebar.project.clearNotifications": "ล้างการแจ้งเตือน",
"app.name.desktop": "OpenCode Desktop",
@@ -625,6 +626,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "เสียงเตือน 01",
"sound.option.alert02": "เสียงเตือน 02",
"sound.option.alert03": "เสียงเตือน 03",
diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts
index 1b40013b60..d0bf86cbba 100644
--- a/packages/app/src/i18n/zh.ts
+++ b/packages/app/src/i18n/zh.ts
@@ -569,6 +569,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。",
"sidebar.project.recentSessions": "最近会话",
"sidebar.project.viewAllSessions": "查看全部会话",
+ "sidebar.project.clearNotifications": "清除通知",
"app.name.desktop": "OpenCode Desktop",
@@ -622,6 +623,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "警报 01",
"sound.option.alert02": "警报 02",
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index 34aec01b9c..349c90b0e1 100644
--- a/packages/app/src/i18n/zht.ts
+++ b/packages/app/src/i18n/zht.ts
@@ -567,6 +567,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "連線任意提供者即可使用更多模型,如 Claude、GPT、Gemini 等。",
"sidebar.project.recentSessions": "最近工作階段",
"sidebar.project.viewAllSessions": "查看全部工作階段",
+ "sidebar.project.clearNotifications": "清除通知",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "桌面",
@@ -620,6 +621,7 @@ export const dict = {
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
"sound.option.alert01": "警報 01",
"sound.option.alert02": "警報 02",
"sound.option.alert03": "警報 03",
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 7eb064f425..7d4a5c0cb8 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -1692,6 +1692,13 @@ export default function Layout(props: ParentProps) {
})
const projectId = createMemo(() => panelProps.project?.id ?? "")
const workspaces = createMemo(() => workspaceIds(panelProps.project))
+ const unseenCount = createMemo(() =>
+ workspaces().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
+ )
+ const clearNotifications = () =>
+ workspaces()
+ .filter((directory) => notification.project.unseenCount(directory) > 0)
+ .forEach((directory) => notification.project.markViewed(directory))
const workspacesEnabled = createMemo(() => {
const project = panelProps.project
if (!project) return false
@@ -1769,6 +1776,16 @@ export default function Layout(props: ParentProps) {
: language.t("sidebar.workspaces.enable")}
+
+
+ {language.t("sidebar.project.clearNotifications")}
+
+
active: Accessor
overlay: Accessor
+ dirs: Accessor
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
onProjectMouseLeave: (worktree: string) => void
onProjectFocus: (worktree: string) => void
@@ -70,73 +72,94 @@ const ProjectTile = (props: {
setMenu: (value: boolean) => void
setOpen: (value: boolean) => void
language: ReturnType
-}): JSX.Element => (
- {
- props.setMenu(value)
- if (value) props.setOpen(false)
- }}
- >
- {
+ const notification = useNotification()
+ const unseenCount = createMemo(() =>
+ props.dirs().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
+ )
+
+ const clear = () =>
+ props
+ .dirs()
+ .filter((directory) => notification.project.unseenCount(directory) > 0)
+ .forEach((directory) => notification.project.markViewed(directory))
+
+ return (
+ {
+ props.setMenu(value)
+ if (value) props.setOpen(false)
}}
- onMouseEnter={(event: MouseEvent) => {
- if (!props.overlay()) return
- props.onProjectMouseEnter(props.project.worktree, event)
- }}
- onMouseLeave={() => {
- if (!props.overlay()) return
- props.onProjectMouseLeave(props.project.worktree)
- }}
- onFocus={() => {
- if (!props.overlay()) return
- props.onProjectFocus(props.project.worktree)
- }}
- onClick={() => props.navigateToProject(props.project.worktree)}
- onBlur={() => props.setOpen(false)}
>
-
-
-
-
- props.showEditProjectDialog(props.project)}>
- {props.language.t("common.edit")}
-
- props.toggleProjectWorkspaces(props.project)}
- >
-
- {props.workspacesEnabled(props.project)
- ? props.language.t("sidebar.workspaces.disable")
- : props.language.t("sidebar.workspaces.enable")}
-
-
-
- props.closeProject(props.project.worktree)}
- >
- {props.language.t("common.close")}
-
-
-
-
-)
+ {
+ if (!props.overlay()) return
+ props.onProjectMouseEnter(props.project.worktree, event)
+ }}
+ onMouseLeave={() => {
+ if (!props.overlay()) return
+ props.onProjectMouseLeave(props.project.worktree)
+ }}
+ onFocus={() => {
+ if (!props.overlay()) return
+ props.onProjectFocus(props.project.worktree)
+ }}
+ onClick={() => props.navigateToProject(props.project.worktree)}
+ onBlur={() => props.setOpen(false)}
+ >
+
+
+
+
+ props.showEditProjectDialog(props.project)}>
+ {props.language.t("common.edit")}
+
+ props.toggleProjectWorkspaces(props.project)}
+ >
+
+ {props.workspacesEnabled(props.project)
+ ? props.language.t("sidebar.workspaces.disable")
+ : props.language.t("sidebar.workspaces.enable")}
+
+
+
+ {props.language.t("sidebar.project.clearNotifications")}
+
+
+ props.closeProject(props.project.worktree)}
+ >
+ {props.language.t("common.close")}
+
+
+
+
+ )
+}
const ProjectPreviewPanel = (props: {
project: LocalProject
@@ -254,6 +277,7 @@ export const SortableProject = (props: {
)
const workspaces = createMemo(() => props.ctx.workspaceIds(props.project).slice(0, 2))
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
+ const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
const [open, setOpen] = createSignal(false)
const [menu, setMenu] = createSignal(false)
@@ -304,6 +328,7 @@ export const SortableProject = (props: {
selected={selected}
active={active}
overlay={overlay}
+ dirs={dirs}
onProjectMouseEnter={props.ctx.onProjectMouseEnter}
onProjectMouseLeave={props.ctx.onProjectMouseLeave}
onProjectFocus={props.ctx.onProjectFocus}
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index ea16083326..a6b2f5685d 100644
--- a/packages/console/app/package.json
+++ b/packages/console/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
- "version": "1.2.2",
+ "version": "1.2.4",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index 8c71ca77b2..f81304c920 100644
--- a/packages/console/core/package.json
+++ b/packages/console/core/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
- "version": "1.2.2",
+ "version": "1.2.4",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index debba52bab..e3864bfad6 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
- "version": "1.2.2",
+ "version": "1.2.4",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json
index e361cc0e73..261d36bae5 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
- "version": "1.2.2",
+ "version": "1.2.4",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index f9ab28cfca..2901e299c0 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
- "version": "1.2.2",
+ "version": "1.2.4",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/desktop/src-tauri/src/windows.rs b/packages/desktop/src-tauri/src/windows.rs
index 2ddcb0506d..056720055b 100644
--- a/packages/desktop/src-tauri/src/windows.rs
+++ b/packages/desktop/src-tauri/src/windows.rs
@@ -22,6 +22,8 @@ impl MainWindow {
pub fn create(app: &AppHandle) -> Result {
if let Some(window) = app.get_webview_window(Self::LABEL) {
+ let _ = window.set_focus();
+ let _ = window.unminimize();
return Ok(Self(window));
}
@@ -50,6 +52,9 @@ impl MainWindow {
let window = window_builder.build()?;
+ // Ensure window is focused after creation (e.g., after update/relaunch)
+ let _ = window.set_focus();
+
setup_window_state_listener(app, &window);
#[cfg(windows)]
diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json
index 173688eb1d..ac7060dd10 100644
--- a/packages/enterprise/package.json
+++ b/packages/enterprise/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
- "version": "1.2.2",
+ "version": "1.2.4",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index 9498ce1269..9c10eb9826 100644
--- a/packages/extensions/zed/extension.toml
+++ b/packages/extensions/zed/extension.toml
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
-version = "1.2.2"
+version = "1.2.4"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.2/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.4/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.2/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.4/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.2/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.4/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.2/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.4/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.2/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.4/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
diff --git a/packages/function/package.json b/packages/function/package.json
index 1718b791a6..3a431e9bdf 100644
--- a/packages/function/package.json
+++ b/packages/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
- "version": "1.2.2",
+ "version": "1.2.4",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 728ec38385..a5b3415550 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
- "version": "1.2.2",
+ "version": "1.2.4",
"name": "opencode",
"type": "module",
"license": "MIT",
diff --git a/packages/opencode/src/cli/cmd/db.ts b/packages/opencode/src/cli/cmd/db.ts
new file mode 100644
index 0000000000..0ade4d3c4b
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/db.ts
@@ -0,0 +1,68 @@
+import type { Argv } from "yargs"
+import { spawn } from "child_process"
+import { Database } from "../../storage/db"
+import { Database as BunDatabase } from "bun:sqlite"
+import { UI } from "../ui"
+import { cmd } from "./cmd"
+
+const QueryCommand = cmd({
+ command: "$0 [query]",
+ describe: "open an interactive sqlite3 shell or run a query",
+ builder: (yargs: Argv) => {
+ return yargs
+ .positional("query", {
+ type: "string",
+ describe: "SQL query to execute",
+ })
+ .option("format", {
+ type: "string",
+ choices: ["json", "tsv"],
+ default: "tsv",
+ describe: "Output format",
+ })
+ },
+ handler: async (args: { query?: string; format: string }) => {
+ const query = args.query as string | undefined
+ if (query) {
+ const db = new BunDatabase(Database.Path, { readonly: true })
+ try {
+ const result = db.query(query).all() as Record[]
+ if (args.format === "json") {
+ console.log(JSON.stringify(result, null, 2))
+ } else if (result.length > 0) {
+ const keys = Object.keys(result[0])
+ console.log(keys.join("\t"))
+ for (const row of result) {
+ console.log(keys.map((k) => row[k]).join("\t"))
+ }
+ }
+ } catch (err) {
+ UI.error(err instanceof Error ? err.message : String(err))
+ process.exit(1)
+ }
+ db.close()
+ return
+ }
+ const child = spawn("sqlite3", [Database.Path], {
+ stdio: "inherit",
+ })
+ await new Promise((resolve) => child.on("close", resolve))
+ },
+})
+
+const PathCommand = cmd({
+ command: "path",
+ describe: "print the database path",
+ handler: () => {
+ console.log(Database.Path)
+ },
+})
+
+export const DbCommand = cmd({
+ command: "db",
+ describe: "database tools",
+ builder: (yargs: Argv) => {
+ return yargs.command(QueryCommand).command(PathCommand).demandCommand()
+ },
+ handler: () => {},
+})
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index 420ead5555..39e77782f5 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -26,6 +26,7 @@ import { EOL } from "os"
import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
+import { DbCommand } from "./cli/cmd/db"
import path from "path"
import { Global } from "./global"
import { JsonMigration } from "./storage/json-migration"
@@ -81,14 +82,14 @@ const cli = yargs(hideBin(process.argv))
const marker = path.join(Global.Path.data, "opencode.db")
if (!(await Bun.file(marker).exists())) {
- console.log("Performing one time database migration, may take a few minutes...")
- const tty = process.stdout.isTTY
+ const tty = process.stderr.isTTY
+ process.stderr.write("Performing one time database migration, may take a few minutes..." + EOL)
const width = 36
const orange = "\x1b[38;5;214m"
const muted = "\x1b[0;2m"
const reset = "\x1b[0m"
let last = -1
- if (tty) process.stdout.write("\x1b[?25l")
+ if (tty) process.stderr.write("\x1b[?25l")
try {
await JsonMigration.run(Database.Client().$client, {
progress: (event) => {
@@ -98,22 +99,22 @@ const cli = yargs(hideBin(process.argv))
if (tty) {
const fill = Math.round((percent / 100) * width)
const bar = `${"■".repeat(fill)}${"・".repeat(width - fill)}`
- process.stdout.write(
+ process.stderr.write(
`\r${orange}${bar} ${percent.toString().padStart(3)}%${reset} ${muted}${event.label.padEnd(12)} ${event.current}/${event.total}${reset}`,
)
- if (event.current === event.total) process.stdout.write("\n")
+ if (event.current === event.total) process.stderr.write("\n")
} else {
- console.log(`sqlite-migration:${percent}`)
+ process.stderr.write(`sqlite-migration:${percent}${EOL}`)
}
},
})
} finally {
- if (tty) process.stdout.write("\x1b[?25h")
+ if (tty) process.stderr.write("\x1b[?25h")
else {
- console.log(`sqlite-migration:done`)
+ process.stderr.write(`sqlite-migration:done${EOL}`)
}
}
- console.log("Database migration complete.")
+ process.stderr.write("Database migration complete." + EOL)
}
})
.usage("\n" + UI.logo())
@@ -138,6 +139,7 @@ const cli = yargs(hideBin(process.argv))
.command(GithubCommand)
.command(PrCommand)
.command(SessionCommand)
+ .command(DbCommand)
.fail((msg, err) => {
if (
msg?.startsWith("Unknown argument") ||
@@ -188,7 +190,7 @@ try {
if (formatted) UI.error(formatted)
if (formatted === undefined) {
UI.error("Unexpected error, check log file at " + Log.file() + " for more details" + EOL)
- console.error(e instanceof Error ? e.message : String(e))
+ process.stderr.write((e instanceof Error ? e.message : String(e)) + EOL)
}
process.exitCode = 1
} finally {
diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts
index 387e93b376..0974cbe7be 100644
--- a/packages/opencode/src/storage/db.ts
+++ b/packages/opencode/src/storage/db.ts
@@ -25,6 +25,7 @@ export const NotFoundError = NamedError.create(
const log = Log.create({ service: "db" })
export namespace Database {
+ export const Path = path.join(Global.Path.data, "opencode.db")
type Schema = typeof schema
export type Transaction = SQLiteTransaction<"sync", void, Schema>
diff --git a/packages/opencode/src/storage/json-migration.ts b/packages/opencode/src/storage/json-migration.ts
index 89d561188c..e0684ce3c1 100644
--- a/packages/opencode/src/storage/json-migration.ts
+++ b/packages/opencode/src/storage/json-migration.ts
@@ -152,6 +152,7 @@ export namespace JsonMigration {
sqlite.exec("BEGIN TRANSACTION")
// Migrate projects first (no FK deps)
+ // Derive all IDs from file paths, not JSON content
const projectIds = new Set()
const projectValues = [] as any[]
for (let i = 0; i < projectFiles.length; i += batchSize) {
@@ -161,13 +162,10 @@ export namespace JsonMigration {
for (let j = 0; j < batch.length; j++) {
const data = batch[j]
if (!data) continue
- if (!data?.id) {
- errs.push(`project missing id: ${projectFiles[i + j]}`)
- continue
- }
- projectIds.add(data.id)
+ const id = path.basename(projectFiles[i + j], ".json")
+ projectIds.add(id)
projectValues.push({
- id: data.id,
+ id,
worktree: data.worktree ?? "/",
vcs: data.vcs,
name: data.name ?? undefined,
@@ -186,6 +184,9 @@ export namespace JsonMigration {
log.info("migrated projects", { count: stats.projects, duration: Math.round(performance.now() - start) })
// Migrate sessions (depends on projects)
+ // Derive all IDs from directory/file paths, not JSON content, since earlier
+ // migrations may have moved sessions to new directories without updating the JSON
+ const sessionProjects = sessionFiles.map((file) => path.basename(path.dirname(file)))
const sessionIds = new Set()
const sessionValues = [] as any[]
for (let i = 0; i < sessionFiles.length; i += batchSize) {
@@ -195,18 +196,16 @@ export namespace JsonMigration {
for (let j = 0; j < batch.length; j++) {
const data = batch[j]
if (!data) continue
- if (!data?.id || !data?.projectID) {
- errs.push(`session missing id or projectID: ${sessionFiles[i + j]}`)
- continue
- }
- if (!projectIds.has(data.projectID)) {
+ const id = path.basename(sessionFiles[i + j], ".json")
+ const projectID = sessionProjects[i + j]
+ if (!projectIds.has(projectID)) {
orphans.sessions++
continue
}
- sessionIds.add(data.id)
+ sessionIds.add(id)
sessionValues.push({
- id: data.id,
- project_id: data.projectID,
+ id,
+ project_id: projectID,
parent_id: data.parentID ?? null,
slug: data.slug ?? "",
directory: data.directory ?? "",
@@ -253,11 +252,7 @@ export namespace JsonMigration {
const data = batch[j]
if (!data) continue
const file = allMessageFiles[i + j]
- const id = data.id ?? path.basename(file, ".json")
- if (!id) {
- errs.push(`message missing id: ${file}`)
- continue
- }
+ const id = path.basename(file, ".json")
const sessionID = allMessageSessions[i + j]
messageSessions.set(id, sessionID)
const rest = data
@@ -287,12 +282,8 @@ export namespace JsonMigration {
const data = batch[j]
if (!data) continue
const file = partFiles[i + j]
- const id = data.id ?? path.basename(file, ".json")
- const messageID = data.messageID ?? path.basename(path.dirname(file))
- if (!id || !messageID) {
- errs.push(`part missing id/messageID/sessionID: ${file}`)
- continue
- }
+ const id = path.basename(file, ".json")
+ const messageID = path.basename(path.dirname(file))
const sessionID = messageSessions.get(messageID)
if (!sessionID) {
errs.push(`part missing message session: ${file}`)
diff --git a/packages/opencode/test/storage/json-migration.test.ts b/packages/opencode/test/storage/json-migration.test.ts
index ff05d6d059..b70c9e1ebe 100644
--- a/packages/opencode/test/storage/json-migration.test.ts
+++ b/packages/opencode/test/storage/json-migration.test.ts
@@ -128,6 +128,28 @@ describe("JSON to SQLite migration", () => {
expect(projects[0].sandboxes).toEqual(["/test/sandbox"])
})
+ test("uses filename for project id when JSON has different value", async () => {
+ await Bun.write(
+ path.join(storageDir, "project", "proj_filename.json"),
+ JSON.stringify({
+ id: "proj_different_in_json", // Stale! Should be ignored
+ worktree: "/test/path",
+ vcs: "git",
+ name: "Test Project",
+ sandboxes: [],
+ }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.projects).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const projects = db.select().from(ProjectTable).all()
+ expect(projects.length).toBe(1)
+ expect(projects[0].id).toBe("proj_filename") // Uses filename, not JSON id
+ })
+
test("migrates project with commands", async () => {
await writeProject(storageDir, {
id: "proj_with_commands",
@@ -285,6 +307,74 @@ describe("JSON to SQLite migration", () => {
expect(parts[0].data).not.toHaveProperty("sessionID")
})
+ test("uses filename for message id when JSON has different value", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+ await Bun.write(
+ path.join(storageDir, "message", "ses_test456def", "msg_from_filename.json"),
+ JSON.stringify({
+ id: "msg_different_in_json", // Stale! Should be ignored
+ sessionID: "ses_test456def",
+ role: "user",
+ agent: "default",
+ time: { created: 1700000000000 },
+ }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.messages).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const messages = db.select().from(MessageTable).all()
+ expect(messages.length).toBe(1)
+ expect(messages[0].id).toBe("msg_from_filename") // Uses filename, not JSON id
+ expect(messages[0].session_id).toBe("ses_test456def")
+ })
+
+ test("uses paths for part id and messageID when JSON has different values", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+ await Bun.write(
+ path.join(storageDir, "message", "ses_test456def", "msg_realmsgid.json"),
+ JSON.stringify({
+ role: "user",
+ agent: "default",
+ time: { created: 1700000000000 },
+ }),
+ )
+ await Bun.write(
+ path.join(storageDir, "part", "msg_realmsgid", "prt_from_filename.json"),
+ JSON.stringify({
+ id: "prt_different_in_json", // Stale! Should be ignored
+ messageID: "msg_different_in_json", // Stale! Should be ignored
+ sessionID: "ses_test456def",
+ type: "text",
+ text: "Hello",
+ }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.parts).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const parts = db.select().from(PartTable).all()
+ expect(parts.length).toBe(1)
+ expect(parts[0].id).toBe("prt_from_filename") // Uses filename, not JSON id
+ expect(parts[0].message_id).toBe("msg_realmsgid") // Uses parent dir, not JSON messageID
+ })
+
test("skips orphaned sessions (no parent project)", async () => {
await Bun.write(
path.join(storageDir, "session", "proj_test123abc", "ses_orphan.json"),
@@ -304,6 +394,72 @@ describe("JSON to SQLite migration", () => {
expect(stats?.sessions).toBe(0)
})
+ test("uses directory path for projectID when JSON has stale value", async () => {
+ // Simulates the scenario where earlier migration moved sessions to new
+ // git-based project directories but didn't update the projectID field
+ const gitBasedProjectID = "abc123gitcommit"
+ await writeProject(storageDir, {
+ id: gitBasedProjectID,
+ worktree: "/test/path",
+ vcs: "git",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+
+ // Session is in the git-based directory but JSON still has old projectID
+ await writeSession(storageDir, gitBasedProjectID, {
+ id: "ses_migrated",
+ projectID: "old-project-name", // Stale! Should be ignored
+ slug: "migrated-session",
+ directory: "/test/path",
+ title: "Migrated Session",
+ version: "1.0.0",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ })
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.sessions).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const sessions = db.select().from(SessionTable).all()
+ expect(sessions.length).toBe(1)
+ expect(sessions[0].id).toBe("ses_migrated")
+ expect(sessions[0].project_id).toBe(gitBasedProjectID) // Uses directory, not stale JSON
+ })
+
+ test("uses filename for session id when JSON has different value", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/test/path",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+
+ await Bun.write(
+ path.join(storageDir, "session", "proj_test123abc", "ses_from_filename.json"),
+ JSON.stringify({
+ id: "ses_different_in_json", // Stale! Should be ignored
+ projectID: "proj_test123abc",
+ slug: "test-session",
+ directory: "/test/path",
+ title: "Test Session",
+ version: "1.0.0",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.sessions).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const sessions = db.select().from(SessionTable).all()
+ expect(sessions.length).toBe(1)
+ expect(sessions[0].id).toBe("ses_from_filename") // Uses filename, not JSON id
+ expect(sessions[0].project_id).toBe("proj_test123abc")
+ })
+
test("is idempotent (running twice doesn't duplicate)", async () => {
await writeProject(storageDir, {
id: "proj_test123abc",
@@ -666,8 +822,11 @@ describe("JSON to SQLite migration", () => {
const stats = await JsonMigration.run(sqlite)
- expect(stats.projects).toBe(1)
- expect(stats.sessions).toBe(1)
+ // Projects: proj_test123abc (valid), proj_missing_id (now derives id from filename)
+ // Sessions: ses_test456def (valid), ses_missing_project (now uses dir path),
+ // ses_orphan (now uses dir path, ignores stale projectID)
+ expect(stats.projects).toBe(2)
+ expect(stats.sessions).toBe(3)
expect(stats.messages).toBe(1)
expect(stats.parts).toBe(1)
expect(stats.todos).toBe(1)
@@ -676,8 +835,8 @@ describe("JSON to SQLite migration", () => {
expect(stats.errors.length).toBeGreaterThanOrEqual(6)
const db = drizzle({ client: sqlite })
- expect(db.select().from(ProjectTable).all().length).toBe(1)
- expect(db.select().from(SessionTable).all().length).toBe(1)
+ expect(db.select().from(ProjectTable).all().length).toBe(2)
+ expect(db.select().from(SessionTable).all().length).toBe(3)
expect(db.select().from(MessageTable).all().length).toBe(1)
expect(db.select().from(PartTable).all().length).toBe(1)
expect(db.select().from(TodoTable).all().length).toBe(1)
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index d078c0028e..437fc09170 100644
--- a/packages/plugin/package.json
+++ b/packages/plugin/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
- "version": "1.2.2",
+ "version": "1.2.4",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index a03d8382db..74c3fdb1ad 100644
--- a/packages/sdk/js/package.json
+++ b/packages/sdk/js/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
- "version": "1.2.2",
+ "version": "1.2.4",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/slack/package.json b/packages/slack/package.json
index 8a6d289ec0..5a5a0e8359 100644
--- a/packages/slack/package.json
+++ b/packages/slack/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
- "version": "1.2.2",
+ "version": "1.2.4",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 8771b397ce..684836335c 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
- "version": "1.2.2",
+ "version": "1.2.4",
"type": "module",
"license": "MIT",
"exports": {
diff --git a/packages/ui/src/components/font.tsx b/packages/ui/src/components/font.tsx
index fa4b6d8c01..bcb8863c89 100644
--- a/packages/ui/src/components/font.tsx
+++ b/packages/ui/src/components/font.tsx
@@ -26,6 +26,8 @@ import ubuntuMono from "../assets/fonts/ubuntu-mono-nerd-font.woff2"
import ubuntuMonoBold from "../assets/fonts/ubuntu-mono-nerd-font-bold.woff2"
import iosevka from "../assets/fonts/iosevka-nerd-font.woff2"
import iosevkaBold from "../assets/fonts/iosevka-nerd-font-bold.woff2"
+import geistMono from "../assets/fonts/GeistMonoNerdFontMono-Regular.woff2"
+import geistMonoBold from "../assets/fonts/GeistMonoNerdFontMono-Bold.woff2"
type MonoFont = {
family: string
@@ -89,6 +91,11 @@ export const MONO_NERD_FONTS = [
regular: iosevka,
bold: iosevkaBold,
},
+ {
+ family: "GeistMono Nerd Font",
+ regular: geistMono,
+ bold: geistMonoBold,
+ },
] satisfies MonoFont[]
const monoNerdCss = MONO_NERD_FONTS.map(
diff --git a/packages/util/package.json b/packages/util/package.json
index c610140398..74393cecee 100644
--- a/packages/util/package.json
+++ b/packages/util/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
- "version": "1.2.2",
+ "version": "1.2.4",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/web/package.json b/packages/web/package.json
index 79858de586..e2acd6cf39 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
- "version": "1.2.2",
+ "version": "1.2.4",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json
index e91d1a324d..e8e41a4f29 100644
--- a/sdks/vscode/package.json
+++ b/sdks/vscode/package.json
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
- "version": "1.2.2",
+ "version": "1.2.4",
"publisher": "sst-dev",
"repository": {
"type": "git",