Compare commits

...

7 Commits
v1.1.59 ... dev

Author SHA1 Message Date
Aiden Cline
6b4d617df0 feat: adjust read tool so that it can handle dirs too (#13090) 2026-02-11 13:23:00 -06:00
Dax Raad
e3471526f4 add square logo variants to brand page 2026-02-11 13:47:54 -05:00
Adam
6b30e0b752 chore: update docs sync workflow 2026-02-11 11:47:32 -06:00
Adam
fbabce1125 fix(app): translations 2026-02-11 11:03:35 -06:00
opencode-agent[bot]
8f56ed5b85 chore: generate 2026-02-11 17:00:20 +00:00
Filip
81b5a6a08b fix(app):workspace reset (#13170)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-02-11 10:59:09 -06:00
opencode-agent[bot]
94cb6390aa chore: generate 2026-02-11 15:37:06 +00:00
33 changed files with 662 additions and 147 deletions

View File

@@ -64,10 +64,13 @@ jobs:
Requirements:
1. Update all relevant locale docs under packages/web/src/content/docs/<locale>/ so they reflect these English page changes.
2. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
3. Keep locale docs structure aligned with their corresponding English pages.
4. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
5. If no locale updates are needed, make no changes.
2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
5. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
6. Keep locale docs structure aligned with their corresponding English pages.
7. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
8. If no locale updates are needed, make no changes.
- name: Commit and push locale docs updates
if: steps.changes.outputs.has_changes == 'true'

View File

@@ -32,6 +32,9 @@ description: Use this when you are working on file operations like reading, writ
- Decode tool stderr with `Bun.readableStreamToText`.
- For large writes, use `Bun.write(Bun.file(path), text)`.
NOTE: Bun.file(...).exists() will return `false` if the value is a directory.
Use Filesystem.exists(...) instead if path can be file or directory
## Quick checklist
- Use Bun APIs first.

View File

@@ -4,6 +4,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSync } from "./global-sync"
import { useGlobalSDK } from "./global-sdk"
import { useServer } from "./server"
import { usePlatform } from "./platform"
import { Project } from "@opencode-ai/sdk/v2"
import { Persist, persisted, removePersisted } from "@/utils/persist"
import { same } from "@/utils/same"
@@ -90,6 +91,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const globalSdk = useGlobalSDK()
const globalSync = useGlobalSync()
const server = useServer()
const platform = usePlatform()
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value)
@@ -200,10 +202,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
for (const entry of SESSION_STATE_KEYS) {
const target = session ? Persist.session(dir, session, entry.key) : Persist.workspace(dir, entry.key)
void removePersisted(target)
void removePersisted(target, platform)
const legacyKey = `${dir}/${entry.legacy}${session ? "/" + session : ""}.${entry.version}`
void removePersisted({ key: legacyKey })
void removePersisted({ key: legacyKey }, platform)
}
}
}

View File

@@ -3,6 +3,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
import { batch, createEffect, createMemo, createRoot, onCleanup } from "solid-js"
import { useParams } from "@solidjs/router"
import { useSDK } from "./sdk"
import type { Platform } from "./platform"
import { Persist, persisted, removePersisted } from "@/utils/persist"
export type LocalPTY = {
@@ -37,14 +38,14 @@ type TerminalCacheEntry = {
const caches = new Set<Map<string, TerminalCacheEntry>>()
export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[]) {
export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], platform?: Platform) {
const key = getWorkspaceTerminalCacheKey(dir)
for (const cache of caches) {
const entry = cache.get(key)
entry?.value.clear()
}
removePersisted(Persist.workspace(dir, "terminal"))
removePersisted(Persist.workspace(dir, "terminal"), platform)
const legacy = new Set(getLegacyTerminalStorageKeys(dir))
for (const id of sessionIDs ?? []) {
@@ -53,7 +54,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[]) {
}
}
for (const key of legacy) {
removePersisted({ key })
removePersisted({ key }, platform)
}
}

View File

@@ -110,25 +110,30 @@ export const dict = {
"provider.connect.status.inProgress": "جارٍ التفويض...",
"provider.connect.status.waiting": "في انتظار التفويض...",
"provider.connect.status.failed": "فشل التفويض: {{error}}",
"provider.connect.apiKey.description": "أدخل مفتاح واجهة برمجة تطبيقات {{provider}} الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.apiKey.description":
"أدخل مفتاح واجهة برمجة تطبيقات {{provider}} الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.apiKey.label": "مفتاح واجهة برمجة تطبيقات {{provider}}",
"provider.connect.apiKey.placeholder": "مفتاح API",
"provider.connect.apiKey.required": "مفتاح API مطلوب",
"provider.connect.opencodeZen.line1": "يمنحك OpenCode Zen الوصول إلى مجموعة مختارة من النماذج الموثوقة والمحسنة لوكلاء البرمجة.",
"provider.connect.opencodeZen.line2": "باستخدام مفتاح API واحد، ستحصل على إمكانية الوصول إلى نماذج مثل Claude و GPT و Gemini و GLM والمزيد.",
"provider.connect.opencodeZen.line1":
"يمنحك OpenCode Zen الوصول إلى مجموعة مختارة من النماذج الموثوقة والمحسنة لوكلاء البرمجة.",
"provider.connect.opencodeZen.line2":
"باستخدام مفتاح API واحد، ستحصل على إمكانية الوصول إلى نماذج مثل Claude و GPT و Gemini و GLM والمزيد.",
"provider.connect.opencodeZen.visit.prefix": "قم بزيارة ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " للحصول على مفتاح API الخاص بك.",
"provider.connect.oauth.code.visit.prefix": "قم بزيارة ",
"provider.connect.oauth.code.visit.link": "هذا الرابط",
"provider.connect.oauth.code.visit.suffix": " للحصول على رمز التفويض الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.oauth.code.visit.suffix":
" للحصول على رمز التفويض الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.oauth.code.label": "رمز تفويض {{method}}",
"provider.connect.oauth.code.placeholder": "رمز التفويض",
"provider.connect.oauth.code.required": "رمز التفويض مطلوب",
"provider.connect.oauth.code.invalid": "رمز التفويض غير صالح",
"provider.connect.oauth.auto.visit.prefix": "قم بزيارة ",
"provider.connect.oauth.auto.visit.link": "هذا الرابط",
"provider.connect.oauth.auto.visit.suffix": " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.oauth.auto.visit.suffix":
" وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "رمز التأكيد",
"provider.connect.toast.connected.title": "تم توصيل {{provider}}",
"provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.",
@@ -200,7 +205,7 @@ export const dict = {
"common.default": "افتراضي",
"common.attachment": "مرفق",
"prompt.placeholder.shell": "أدخل أمر shell...",
"prompt.placeholder.normal": "اسأل أي شيء... \"{{example}}\"",
"prompt.placeholder.normal": 'اسأل أي شيء... "{{example}}"',
"prompt.placeholder.summarizeComments": "لخّص التعليقات…",
"prompt.placeholder.summarizeComment": "لخّص التعليق…",
"prompt.mode.shell": "Shell",
@@ -278,7 +283,8 @@ export const dict = {
"dialog.server.add.checking": "جارٍ التحقق...",
"dialog.server.add.button": "إضافة خادم",
"dialog.server.default.title": "الخادم الافتراضي",
"dialog.server.default.description": "الاتصال بهذا الخادم عند بدء تشغيل التطبيق بدلاً من بدء خادم محلي. يتطلب إعادة التشغيل.",
"dialog.server.default.description":
"الاتصال بهذا الخادم عند بدء تشغيل التطبيق بدلاً من بدء خادم محلي. يتطلب إعادة التشغيل.",
"dialog.server.default.none": "لم يتم تحديد خادم",
"dialog.server.default.set": "تعيين الخادم الحالي كافتراضي",
"dialog.server.default.clear": "مسح",
@@ -301,7 +307,7 @@ export const dict = {
"dialog.project.edit.worktree.startup.description": "يتم تشغيله بعد إنشاء مساحة عمل جديدة (شجرة عمل).",
"dialog.project.edit.worktree.startup.placeholder": "مثال: bun install",
"context.breakdown.title": "تفصيل السياق",
"context.breakdown.note": "تفصيل تقريبي لرموز الإدخال. يشمل \"أخرى\" تعريفات الأدوات والنفقات العامة.",
"context.breakdown.note": 'تفصيل تقريبي لرموز الإدخال. يشمل "أخرى" تعريفات الأدوات والنفقات العامة.',
"context.breakdown.system": "النظام",
"context.breakdown.user": "المستخدم",
"context.breakdown.assistant": "المساعد",
@@ -388,7 +394,8 @@ export const dict = {
"error.page.report.prefix": "يرجى الإبلاغ عن هذا الخطأ لفريق OpenCode",
"error.page.report.discord": "على Discord",
"error.page.version": "الإصدار: {{version}}",
"error.dev.rootNotFound": "لم يتم العثور على العنصر الجذري. هل نسيت إضافته إلى index.html؟ أو ربما تمت كتابة سمة id بشكل خاطئ؟",
"error.dev.rootNotFound":
"لم يتم العثور على العنصر الجذري. هل نسيت إضافته إلى index.html؟ أو ربما تمت كتابة سمة id بشكل خاطئ؟",
"error.globalSync.connectFailed": "تعذر الاتصال بالخادم. هل هناك خادم يعمل في `{{url}}`؟",
"directory.error.invalidUrl": "دليل غير صالح في عنوان URL.",
"error.chain.unknown": "خطأ غير معروف",
@@ -400,12 +407,13 @@ export const dict = {
"error.chain.didYouMean": "هل كنت تعني: {{suggestions}}",
"error.chain.modelNotFound": "النموذج غير موجود: {{provider}}/{{model}}",
"error.chain.checkConfig": "تحقق من أسماء الموفر/النموذج في التكوين (opencode.json)",
"error.chain.mcpFailed": "فشل خادم MCP \"{{name}}\". لاحظ أن OpenCode لا يدعم مصادقة MCP بعد.",
"error.chain.mcpFailed": 'فشل خادم MCP "{{name}}". لاحظ أن OpenCode لا يدعم مصادقة MCP بعد.',
"error.chain.providerAuthFailed": "فشلت مصادقة الموفر ({{provider}}): {{message}}",
"error.chain.providerInitFailed": "فشل تهيئة الموفر \"{{provider}}\". تحقق من بيانات الاعتماد والتكوين.",
"error.chain.providerInitFailed": 'فشل تهيئة الموفر "{{provider}}". تحقق من بيانات الاعتماد والتكوين.',
"error.chain.configJsonInvalid": "ملف التكوين في {{path}} ليس JSON(C) صالحًا",
"error.chain.configJsonInvalidWithMessage": "ملف التكوين في {{path}} ليس JSON(C) صالحًا: {{message}}",
"error.chain.configDirectoryTypo": "الدليل \"{{dir}}\" في {{path}} غير صالح. أعد تسمية الدليل إلى \"{{suggestion}}\" أو قم بإزالته. هذا خطأ مطبعي شائع.",
"error.chain.configDirectoryTypo":
'الدليل "{{dir}}" في {{path}} غير صالح. أعد تسمية الدليل إلى "{{suggestion}}" أو قم بإزالته. هذا خطأ مطبعي شائع.',
"error.chain.configFrontmatterError": "فشل تحليل frontmatter في {{path}}:\n{{message}}",
"error.chain.configInvalid": "ملف التكوين في {{path}} غير صالح",
"error.chain.configInvalidWithMessage": "ملف التكوين في {{path}} غير صالح: {{message}}",
@@ -524,9 +532,10 @@ export const dict = {
"settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
"settings.general.row.wayland.tooltip": "على Linux مع شاشات بمعدلات تحديث مختلطة، يمكن أن يكون Wayland الأصلي أكثر استقرارًا.",
"settings.general.row.wayland.tooltip":
"على Linux مع شاشات بمعدلات تحديث مختلطة، يمكن أن يكون Wayland الأصلي أكثر استقرارًا.",
"settings.general.row.releaseNotes.title": "ملاحظات الإصدار",
"settings.general.row.releaseNotes.description": "عرض نوافذ \"ما الجديد\" المنبثقة بعد التحديثات",
"settings.general.row.releaseNotes.description": 'عرض نوافذ "ما الجديد" المنبثقة بعد التحديثات',
"settings.updates.row.startup.title": "التحقق من التحديثات عند بدء التشغيل",
"settings.updates.row.startup.description": "التحقق تلقائيًا من التحديثات عند تشغيل OpenCode",
"settings.updates.row.check.title": "التحقق من التحديثات",
@@ -647,7 +656,8 @@ export const dict = {
"settings.permissions.tool.read.title": "قراءة",
"settings.permissions.tool.read.description": "قراءة ملف (يطابق مسار الملف)",
"settings.permissions.tool.edit.title": "تحرير",
"settings.permissions.tool.edit.description": "تعديل الملفات، بما في ذلك التحرير والكتابة والتصحيحات والتحرير المتعدد",
"settings.permissions.tool.edit.description":
"تعديل الملفات، بما في ذلك التحرير والكتابة والتصحيحات والتحرير المتعدد",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "مطابقة الملفات باستخدام أنماط glob",
"settings.permissions.tool.grep.title": "Grep",
@@ -678,7 +688,7 @@ export const dict = {
"settings.permissions.tool.doom_loop.description": "اكتشاف استدعاءات الأدوات المتكررة بمدخلات متطابقة",
"session.delete.failed.title": "فشل حذف الجلسة",
"session.delete.title": "حذف الجلسة",
"session.delete.confirm": "حذف الجلسة \"{{name}}\",
"session.delete.confirm": 'حذف الجلسة "{{name}}"؟',
"session.delete.button": "حذف الجلسة",
"workspace.new": "مساحة عمل جديدة",
"workspace.type.local": "محلي",
@@ -696,14 +706,13 @@ export const dict = {
"workspace.status.clean": "لم يتم اكتشاف تغييرات غير مدمجة.",
"workspace.status.dirty": "تم اكتشاف تغييرات غير مدمجة في مساحة العمل هذه.",
"workspace.delete.title": "حذف مساحة العمل",
"workspace.delete.confirm": "حذف مساحة العمل \"{{name}}\",
"workspace.delete.confirm": 'حذف مساحة العمل "{{name}}"؟',
"workspace.delete.button": "حذف مساحة العمل",
"workspace.reset.title": "إعادة تعيين مساحة العمل",
"workspace.reset.confirm": "إعادة تعيين مساحة العمل \"{{name}}\",
"workspace.reset.confirm": 'إعادة تعيين مساحة العمل "{{name}}"؟',
"workspace.reset.button": "إعادة تعيين مساحة العمل",
"workspace.reset.archived.none": "لن تتم أرشفة أي جلسات نشطة.",
"workspace.reset.archived.one": "ستتم أرشفة جلسة واحدة.",
"workspace.reset.archived.many": "ستتم أرشفة {{count}} جلسات.",
"workspace.reset.note": "سيؤدي هذا إلى إعادة تعيين مساحة العمل لتتطابق مع الفرع الافتراضي.",
}

View File

@@ -110,25 +110,30 @@ export const dict = {
"provider.connect.status.inProgress": "Autorização em andamento...",
"provider.connect.status.waiting": "Aguardando autorização...",
"provider.connect.status.failed": "Autorização falhou: {{error}}",
"provider.connect.apiKey.description": "Digite sua chave de API do {{provider}} para conectar sua conta e usar modelos do {{provider}} no OpenCode.",
"provider.connect.apiKey.description":
"Digite sua chave de API do {{provider}} para conectar sua conta e usar modelos do {{provider}} no OpenCode.",
"provider.connect.apiKey.label": "Chave de API do {{provider}}",
"provider.connect.apiKey.placeholder": "Chave de API",
"provider.connect.apiKey.required": "A chave de API é obrigatória",
"provider.connect.opencodeZen.line1": "OpenCode Zen oferece acesso a um conjunto selecionado de modelos confiáveis otimizados para agentes de código.",
"provider.connect.opencodeZen.line2": "Com uma única chave de API você terá acesso a modelos como Claude, GPT, Gemini, GLM e mais.",
"provider.connect.opencodeZen.line1":
"OpenCode Zen oferece acesso a um conjunto selecionado de modelos confiáveis otimizados para agentes de código.",
"provider.connect.opencodeZen.line2":
"Com uma única chave de API você terá acesso a modelos como Claude, GPT, Gemini, GLM e mais.",
"provider.connect.opencodeZen.visit.prefix": "Visite ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " para obter sua chave de API.",
"provider.connect.oauth.code.visit.prefix": "Visite ",
"provider.connect.oauth.code.visit.link": "este link",
"provider.connect.oauth.code.visit.suffix": " para obter seu código de autorização e conectar sua conta para usar modelos do {{provider}} no OpenCode.",
"provider.connect.oauth.code.visit.suffix":
" para obter seu código de autorização e conectar sua conta para usar modelos do {{provider}} no OpenCode.",
"provider.connect.oauth.code.label": "Código de autorização {{method}}",
"provider.connect.oauth.code.placeholder": "Código de autorização",
"provider.connect.oauth.code.required": "O código de autorização é obrigatório",
"provider.connect.oauth.code.invalid": "Código de autorização inválido",
"provider.connect.oauth.auto.visit.prefix": "Visite ",
"provider.connect.oauth.auto.visit.link": "este link",
"provider.connect.oauth.auto.visit.suffix": " e digite o código abaixo para conectar sua conta e usar modelos do {{provider}} no OpenCode.",
"provider.connect.oauth.auto.visit.suffix":
" e digite o código abaixo para conectar sua conta e usar modelos do {{provider}} no OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Código de confirmação",
"provider.connect.toast.connected.title": "{{provider}} conectado",
"provider.connect.toast.connected.description": "Modelos do {{provider}} agora estão disponíveis para uso.",
@@ -200,7 +205,7 @@ export const dict = {
"common.default": "Padrão",
"common.attachment": "anexo",
"prompt.placeholder.shell": "Digite comando do shell...",
"prompt.placeholder.normal": "Pergunte qualquer coisa... \"{{example}}\"",
"prompt.placeholder.normal": 'Pergunte qualquer coisa... "{{example}}"',
"prompt.placeholder.summarizeComments": "Resumir comentários…",
"prompt.placeholder.summarizeComment": "Resumir comentário…",
"prompt.mode.shell": "Shell",
@@ -278,7 +283,8 @@ export const dict = {
"dialog.server.add.checking": "Verificando...",
"dialog.server.add.button": "Adicionar",
"dialog.server.default.title": "Servidor padrão",
"dialog.server.default.description": "Conectar a este servidor na inicialização do aplicativo ao invés de iniciar um servidor local. Requer reinicialização.",
"dialog.server.default.description":
"Conectar a este servidor na inicialização do aplicativo ao invés de iniciar um servidor local. Requer reinicialização.",
"dialog.server.default.none": "Nenhum servidor selecionado",
"dialog.server.default.set": "Definir servidor atual como padrão",
"dialog.server.default.clear": "Limpar",
@@ -301,7 +307,8 @@ export const dict = {
"dialog.project.edit.worktree.startup.description": "Executa após criar um novo espaço de trabalho (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "ex: bun install",
"context.breakdown.title": "Detalhamento do Contexto",
"context.breakdown.note": "Detalhamento aproximado dos tokens de entrada. \"Outros\" inclui definições de ferramentas e overhead.",
"context.breakdown.note":
'Detalhamento aproximado dos tokens de entrada. "Outros" inclui definições de ferramentas e overhead.',
"context.breakdown.system": "Sistema",
"context.breakdown.user": "Usuário",
"context.breakdown.assistant": "Assistente",
@@ -388,7 +395,8 @@ export const dict = {
"error.page.report.prefix": "Por favor, reporte este erro para a equipe do OpenCode",
"error.page.report.discord": "no Discord",
"error.page.version": "Versão: {{version}}",
"error.dev.rootNotFound": "Elemento raiz não encontrado. Você esqueceu de adicioná-lo ao seu index.html? Ou talvez o atributo id foi escrito incorretamente?",
"error.dev.rootNotFound":
"Elemento raiz não encontrado. Você esqueceu de adicioná-lo ao seu index.html? Ou talvez o atributo id foi escrito incorretamente?",
"error.globalSync.connectFailed": "Não foi possível conectar ao servidor. Há um servidor executando em `{{url}}`?",
"directory.error.invalidUrl": "Diretório inválido na URL.",
"error.chain.unknown": "Erro desconhecido",
@@ -400,12 +408,15 @@ export const dict = {
"error.chain.didYouMean": "Você quis dizer: {{suggestions}}",
"error.chain.modelNotFound": "Modelo não encontrado: {{provider}}/{{model}}",
"error.chain.checkConfig": "Verifique os nomes de provedor/modelo na sua configuração (opencode.json)",
"error.chain.mcpFailed": "Servidor MCP \"{{name}}\" falhou. Nota: OpenCode ainda não suporta autenticação MCP.",
"error.chain.mcpFailed": 'Servidor MCP "{{name}}" falhou. Nota: OpenCode ainda não suporta autenticação MCP.',
"error.chain.providerAuthFailed": "Autenticação do provedor falhou ({{provider}}): {{message}}",
"error.chain.providerInitFailed": "Falha ao inicializar provedor \"{{provider}}\". Verifique credenciais e configuração.",
"error.chain.providerInitFailed":
'Falha ao inicializar provedor "{{provider}}". Verifique credenciais e configuração.',
"error.chain.configJsonInvalid": "Arquivo de configuração em {{path}} não é um JSON(C) válido",
"error.chain.configJsonInvalidWithMessage": "Arquivo de configuração em {{path}} não é um JSON(C) válido: {{message}}",
"error.chain.configDirectoryTypo": "Diretório \"{{dir}}\" em {{path}} não é válido. Renomeie o diretório para \"{{suggestion}}\" ou remova-o. Este é um erro de digitação comum.",
"error.chain.configJsonInvalidWithMessage":
"Arquivo de configuração em {{path}} não é um JSON(C) válido: {{message}}",
"error.chain.configDirectoryTypo":
'Diretório "{{dir}}" em {{path}} não é válido. Renomeie o diretório para "{{suggestion}}" ou remova-o. Este é um erro de digitação comum.',
"error.chain.configFrontmatterError": "Falha ao analisar frontmatter em {{path}}:\n{{message}}",
"error.chain.configInvalid": "Arquivo de configuração em {{path}} é inválido",
"error.chain.configInvalidWithMessage": "Arquivo de configuração em {{path}} é inválido: {{message}}",
@@ -458,8 +469,10 @@ export const dict = {
"status.popover.tab.plugins": "Plugins",
"status.popover.action.manageServers": "Gerenciar servidores",
"session.share.popover.title": "Publicar na web",
"session.share.popover.description.shared": "Esta sessão é pública na web. Está acessível para qualquer pessoa com o link.",
"session.share.popover.description.unshared": "Compartilhar sessão publicamente na web. Estará acessível para qualquer pessoa com o link.",
"session.share.popover.description.shared":
"Esta sessão é pública na web. Está acessível para qualquer pessoa com o link.",
"session.share.popover.description.unshared":
"Compartilhar sessão publicamente na web. Estará acessível para qualquer pessoa com o link.",
"session.share.action.share": "Compartilhar",
"session.share.action.publish": "Publicar",
"session.share.action.publishing": "Publicando...",
@@ -476,7 +489,8 @@ export const dict = {
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Fechar terminal",
"terminal.connectionLost.title": "Conexão Perdida",
"terminal.connectionLost.description": "A conexão do terminal foi interrompida. Isso pode acontecer quando o servidor reinicia.",
"terminal.connectionLost.description":
"A conexão do terminal foi interrompida. Isso pode acontecer quando o servidor reinicia.",
"common.closeTab": "Fechar aba",
"common.dismiss": "Descartar",
"common.requestFailed": "Requisição falhou",
@@ -524,9 +538,10 @@ export const dict = {
"settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
"settings.general.row.wayland.title": "Usar Wayland nativo",
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
"settings.general.row.wayland.tooltip": "No Linux com monitores de taxas de atualização mistas, Wayland nativo pode ser mais estável.",
"settings.general.row.wayland.tooltip":
"No Linux com monitores de taxas de atualização mistas, Wayland nativo pode ser mais estável.",
"settings.general.row.releaseNotes.title": "Notas da versão",
"settings.general.row.releaseNotes.description": "Mostrar pop-ups de \"Novidades\" após atualizações",
"settings.general.row.releaseNotes.description": 'Mostrar pop-ups de "Novidades" após atualizações',
"settings.updates.row.startup.title": "Verificar atualizações ao iniciar",
"settings.updates.row.startup.description": "Verificar atualizações automaticamente quando o OpenCode iniciar",
"settings.updates.row.check.title": "Verificar atualizações",
@@ -593,9 +608,11 @@ export const dict = {
"sound.option.yup05": "Sim 05",
"sound.option.yup06": "Sim 06",
"settings.general.notifications.agent.title": "Agente",
"settings.general.notifications.agent.description": "Mostrar notificação do sistema quando o agente estiver completo ou precisar de atenção",
"settings.general.notifications.agent.description":
"Mostrar notificação do sistema quando o agente estiver completo ou precisar de atenção",
"settings.general.notifications.permissions.title": "Permissões",
"settings.general.notifications.permissions.description": "Mostrar notificação do sistema quando uma permissão for necessária",
"settings.general.notifications.permissions.description":
"Mostrar notificação do sistema quando uma permissão for necessária",
"settings.general.notifications.errors.title": "Erros",
"settings.general.notifications.errors.description": "Mostrar notificação do sistema quando ocorrer um erro",
"settings.general.sounds.agent.title": "Agente",
@@ -647,7 +664,8 @@ export const dict = {
"settings.permissions.tool.read.title": "Ler",
"settings.permissions.tool.read.description": "Ler um arquivo (corresponde ao caminho do arquivo)",
"settings.permissions.tool.edit.title": "Editar",
"settings.permissions.tool.edit.description": "Modificar arquivos, incluindo edições, escritas, patches e multi-edições",
"settings.permissions.tool.edit.description":
"Modificar arquivos, incluindo edições, escritas, patches e multi-edições",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Corresponder arquivos usando padrões glob",
"settings.permissions.tool.grep.title": "Grep",
@@ -678,7 +696,7 @@ export const dict = {
"settings.permissions.tool.doom_loop.description": "Detectar chamadas de ferramentas repetidas com entrada idêntica",
"session.delete.failed.title": "Falha ao excluir sessão",
"session.delete.title": "Excluir sessão",
"session.delete.confirm": "Excluir sessão \"{{name}}\"?",
"session.delete.confirm": 'Excluir sessão "{{name}}"?',
"session.delete.button": "Excluir sessão",
"workspace.new": "Novo espaço de trabalho",
"workspace.type.local": "local",
@@ -696,14 +714,13 @@ export const dict = {
"workspace.status.clean": "Nenhuma alteração não mesclada detectada.",
"workspace.status.dirty": "Alterações não mescladas detectadas neste espaço de trabalho.",
"workspace.delete.title": "Excluir espaço de trabalho",
"workspace.delete.confirm": "Excluir espaço de trabalho \"{{name}}\"?",
"workspace.delete.confirm": 'Excluir espaço de trabalho "{{name}}"?',
"workspace.delete.button": "Excluir espaço de trabalho",
"workspace.reset.title": "Redefinir espaço de trabalho",
"workspace.reset.confirm": "Redefinir espaço de trabalho \"{{name}}\"?",
"workspace.reset.confirm": 'Redefinir espaço de trabalho "{{name}}"?',
"workspace.reset.button": "Redefinir espaço de trabalho",
"workspace.reset.archived.none": "Nenhuma sessão ativa será arquivada.",
"workspace.reset.archived.one": "1 sessão será arquivada.",
"workspace.reset.archived.many": "{{count}} sessões serão arquivadas.",
"workspace.reset.note": "Isso redefinirá o espaço de trabalho para corresponder ao branch padrão.",
}

View File

@@ -114,25 +114,30 @@ export const dict = {
"provider.connect.status.inProgress": "Autorisierung läuft...",
"provider.connect.status.waiting": "Warten auf Autorisierung...",
"provider.connect.status.failed": "Autorisierung fehlgeschlagen: {{error}}",
"provider.connect.apiKey.description": "Geben Sie Ihren {{provider}} API-Schlüssel ein, um Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.apiKey.description":
"Geben Sie Ihren {{provider}} API-Schlüssel ein, um Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.apiKey.label": "{{provider}} API-Schlüssel",
"provider.connect.apiKey.placeholder": "API-Schlüssel",
"provider.connect.apiKey.required": "API-Schlüssel ist erforderlich",
"provider.connect.opencodeZen.line1": "OpenCode Zen bietet Ihnen Zugriff auf eine kuratierte Auswahl zuverlässiger, optimierter Modelle für Coding-Agenten.",
"provider.connect.opencodeZen.line2": "Mit einem einzigen API-Schlüssel erhalten Sie Zugriff auf Modelle wie Claude, GPT, Gemini, GLM und mehr.",
"provider.connect.opencodeZen.line1":
"OpenCode Zen bietet Ihnen Zugriff auf eine kuratierte Auswahl zuverlässiger, optimierter Modelle für Coding-Agenten.",
"provider.connect.opencodeZen.line2":
"Mit einem einzigen API-Schlüssel erhalten Sie Zugriff auf Modelle wie Claude, GPT, Gemini, GLM und mehr.",
"provider.connect.opencodeZen.visit.prefix": "Besuchen Sie ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": ", um Ihren API-Schlüssel zu erhalten.",
"provider.connect.oauth.code.visit.prefix": "Besuchen Sie ",
"provider.connect.oauth.code.visit.link": "diesen Link",
"provider.connect.oauth.code.visit.suffix": ", um Ihren Autorisierungscode zu erhalten, Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.oauth.code.visit.suffix":
", um Ihren Autorisierungscode zu erhalten, Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.oauth.code.label": "{{method}} Autorisierungscode",
"provider.connect.oauth.code.placeholder": "Autorisierungscode",
"provider.connect.oauth.code.required": "Autorisierungscode ist erforderlich",
"provider.connect.oauth.code.invalid": "Ungültiger Autorisierungscode",
"provider.connect.oauth.auto.visit.prefix": "Besuchen Sie ",
"provider.connect.oauth.auto.visit.link": "diesen Link",
"provider.connect.oauth.auto.visit.suffix": " und geben Sie den untenstehenden Code ein, um Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.oauth.auto.visit.suffix":
" und geben Sie den untenstehenden Code ein, um Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.",
"provider.connect.oauth.auto.confirmationCode": "Bestätigungscode",
"provider.connect.toast.connected.title": "{{provider}} verbunden",
"provider.connect.toast.connected.description": "{{provider}} Modelle sind jetzt verfügbar.",
@@ -149,7 +154,8 @@ export const dict = {
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
"provider.custom.field.apiKey.label": "API-Schlüssel",
"provider.custom.field.apiKey.placeholder": "API-Schlüssel",
"provider.custom.field.apiKey.description": "Optional. Leer lassen, wenn Sie die Authentifizierung über Header verwalten.",
"provider.custom.field.apiKey.description":
"Optional. Leer lassen, wenn Sie die Authentifizierung über Header verwalten.",
"provider.custom.models.label": "Modelle",
"provider.custom.models.id.label": "ID",
"provider.custom.models.id.placeholder": "model-id",
@@ -204,7 +210,7 @@ export const dict = {
"common.default": "Standard",
"common.attachment": "Anhang",
"prompt.placeholder.shell": "Shell-Befehl eingeben...",
"prompt.placeholder.normal": "Fragen Sie alles... \"{{example}}\"",
"prompt.placeholder.normal": 'Fragen Sie alles... "{{example}}"',
"prompt.placeholder.summarizeComments": "Kommentare zusammenfassen…",
"prompt.placeholder.summarizeComment": "Kommentar zusammenfassen…",
"prompt.mode.shell": "Shell",
@@ -252,7 +258,8 @@ export const dict = {
"prompt.toast.pasteUnsupported.title": "Nicht unterstütztes Einfügen",
"prompt.toast.pasteUnsupported.description": "Hier können nur Bilder oder PDFs eingefügt werden.",
"prompt.toast.modelAgentRequired.title": "Wählen Sie einen Agenten und ein Modell",
"prompt.toast.modelAgentRequired.description": "Wählen Sie einen Agenten und ein Modell, bevor Sie eine Eingabe senden.",
"prompt.toast.modelAgentRequired.description":
"Wählen Sie einen Agenten und ein Modell, bevor Sie eine Eingabe senden.",
"prompt.toast.worktreeCreateFailed.title": "Worktree konnte nicht erstellt werden",
"prompt.toast.sessionCreateFailed.title": "Sitzung konnte nicht erstellt werden",
"prompt.toast.shellSendFailed.title": "Shell-Befehl konnte nicht gesendet werden",
@@ -282,7 +289,8 @@ export const dict = {
"dialog.server.add.checking": "Prüfen...",
"dialog.server.add.button": "Server hinzufügen",
"dialog.server.default.title": "Standardserver",
"dialog.server.default.description": "Beim App-Start mit diesem Server verbinden, anstatt einen lokalen Server zu starten. Erfordert Neustart.",
"dialog.server.default.description":
"Beim App-Start mit diesem Server verbinden, anstatt einen lokalen Server zu starten. Erfordert Neustart.",
"dialog.server.default.none": "Kein Server ausgewählt",
"dialog.server.default.set": "Aktuellen Server als Standard setzen",
"dialog.server.default.clear": "Löschen",
@@ -302,10 +310,12 @@ export const dict = {
"dialog.project.edit.color": "Farbe",
"dialog.project.edit.color.select": "{{color}}-Farbe auswählen",
"dialog.project.edit.worktree.startup": "Startup-Skript für Arbeitsbereich",
"dialog.project.edit.worktree.startup.description": "Wird nach dem Erstellen eines neuen Arbeitsbereichs (Worktree) ausgeführt.",
"dialog.project.edit.worktree.startup.description":
"Wird nach dem Erstellen eines neuen Arbeitsbereichs (Worktree) ausgeführt.",
"dialog.project.edit.worktree.startup.placeholder": "z. B. bun install",
"context.breakdown.title": "Kontext-Aufschlüsselung",
"context.breakdown.note": "Ungefähre Aufschlüsselung der Eingabe-Token. \"Andere\" beinhaltet Werkzeugdefinitionen und Overhead.",
"context.breakdown.note":
'Ungefähre Aufschlüsselung der Eingabe-Token. "Andere" beinhaltet Werkzeugdefinitionen und Overhead.',
"context.breakdown.system": "System",
"context.breakdown.user": "Benutzer",
"context.breakdown.assistant": "Assistent",
@@ -392,7 +402,8 @@ export const dict = {
"error.page.report.prefix": "Bitte melden Sie diesen Fehler dem OpenCode-Team",
"error.page.report.discord": "auf Discord",
"error.page.version": "Version: {{version}}",
"error.dev.rootNotFound": "Wurzelelement nicht gefunden. Haben Sie vergessen, es in Ihre index.html aufzunehmen? Oder wurde das id-Attribut falsch geschrieben?",
"error.dev.rootNotFound":
"Wurzelelement nicht gefunden. Haben Sie vergessen, es in Ihre index.html aufzunehmen? Oder wurde das id-Attribut falsch geschrieben?",
"error.globalSync.connectFailed": "Verbindung zum Server fehlgeschlagen. Läuft ein Server unter `{{url}}`?",
"directory.error.invalidUrl": "Ungültiges Verzeichnis in der URL.",
"error.chain.unknown": "Unbekannter Fehler",
@@ -404,12 +415,16 @@ export const dict = {
"error.chain.didYouMean": "Meinten Sie: {{suggestions}}",
"error.chain.modelNotFound": "Modell nicht gefunden: {{provider}}/{{model}}",
"error.chain.checkConfig": "Überprüfen Sie Ihre Konfiguration (opencode.json) auf Anbieter-/Modellnamen",
"error.chain.mcpFailed": "MCP-Server \"{{name}}\" fehlgeschlagen. Hinweis: OpenCode unterstützt noch keine MCP-Authentifizierung.",
"error.chain.mcpFailed":
'MCP-Server "{{name}}" fehlgeschlagen. Hinweis: OpenCode unterstützt noch keine MCP-Authentifizierung.',
"error.chain.providerAuthFailed": "Anbieter-Authentifizierung fehlgeschlagen ({{provider}}): {{message}}",
"error.chain.providerInitFailed": "Anbieter \"{{provider}}\" konnte nicht initialisiert werden. Überprüfen Sie Anmeldeinformationen und Konfiguration.",
"error.chain.providerInitFailed":
'Anbieter "{{provider}}" konnte nicht initialisiert werden. Überprüfen Sie Anmeldeinformationen und Konfiguration.',
"error.chain.configJsonInvalid": "Konfigurationsdatei unter {{path}} ist kein gültiges JSON(C)",
"error.chain.configJsonInvalidWithMessage": "Konfigurationsdatei unter {{path}} ist kein gültiges JSON(C): {{message}}",
"error.chain.configDirectoryTypo": "Verzeichnis \"{{dir}}\" in {{path}} ist ungültig. Benennen Sie das Verzeichnis in \"{{suggestion}}\" um oder entfernen Sie es. Dies ist ein häufiger Tippfehler.",
"error.chain.configJsonInvalidWithMessage":
"Konfigurationsdatei unter {{path}} ist kein gültiges JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'Verzeichnis "{{dir}}" in {{path}} ist ungültig. Benennen Sie das Verzeichnis in "{{suggestion}}" um oder entfernen Sie es. Dies ist ein häufiger Tippfehler.',
"error.chain.configFrontmatterError": "Frontmatter in {{path}} konnte nicht geparst werden:\n{{message}}",
"error.chain.configInvalid": "Konfigurationsdatei unter {{path}} ist ungültig",
"error.chain.configInvalidWithMessage": "Konfigurationsdatei unter {{path}} ist ungültig: {{message}}",
@@ -462,8 +477,10 @@ export const dict = {
"status.popover.tab.plugins": "Plugins",
"status.popover.action.manageServers": "Server verwalten",
"session.share.popover.title": "Im Web veröffentlichen",
"session.share.popover.description.shared": "Diese Sitzung ist öffentlich im Web. Sie ist für jeden mit dem Link zugänglich.",
"session.share.popover.description.unshared": "Sitzung öffentlich im Web teilen. Sie wird für jeden mit dem Link zugänglich sein.",
"session.share.popover.description.shared":
"Diese Sitzung ist öffentlich im Web. Sie ist für jeden mit dem Link zugänglich.",
"session.share.popover.description.unshared":
"Sitzung öffentlich im Web teilen. Sie wird für jeden mit dem Link zugänglich sein.",
"session.share.action.share": "Teilen",
"session.share.action.publish": "Veröffentlichen",
"session.share.action.publishing": "Veröffentliche...",
@@ -480,7 +497,8 @@ export const dict = {
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Terminal schließen",
"terminal.connectionLost.title": "Verbindung verloren",
"terminal.connectionLost.description": "Die Terminalverbindung wurde unterbrochen. Das kann passieren, wenn der Server neu startet.",
"terminal.connectionLost.description":
"Die Terminalverbindung wurde unterbrochen. Das kann passieren, wenn der Server neu startet.",
"common.closeTab": "Tab schließen",
"common.dismiss": "Verwerfen",
"common.requestFailed": "Anfrage fehlgeschlagen",
@@ -502,7 +520,8 @@ export const dict = {
"sidebar.workspaces.disable": "Arbeitsbereiche deaktivieren",
"sidebar.gettingStarted.title": "Erste Schritte",
"sidebar.gettingStarted.line1": "OpenCode enthält kostenlose Modelle, damit Sie sofort loslegen können.",
"sidebar.gettingStarted.line2": "Verbinden Sie einen beliebigen Anbieter, um Modelle wie Claude, GPT, Gemini usw. zu nutzen.",
"sidebar.gettingStarted.line2":
"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",
"app.name.desktop": "OpenCode Desktop",
@@ -528,9 +547,10 @@ export const dict = {
"settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
"settings.general.row.wayland.title": "Natives Wayland verwenden",
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
"settings.general.row.wayland.tooltip": "Unter Linux mit Monitoren unterschiedlicher Bildwiederholraten kann natives Wayland stabiler sein.",
"settings.general.row.wayland.tooltip":
"Unter Linux mit Monitoren unterschiedlicher Bildwiederholraten kann natives Wayland stabiler sein.",
"settings.general.row.releaseNotes.title": "Versionshinweise",
"settings.general.row.releaseNotes.description": "\"Neuigkeiten\"-Pop-ups nach Updates anzeigen",
"settings.general.row.releaseNotes.description": '"Neuigkeiten"-Pop-ups nach Updates anzeigen',
"settings.updates.row.startup.title": "Beim Start nach Updates suchen",
"settings.updates.row.startup.description": "Beim Start von OpenCode automatisch nach Updates suchen",
"settings.updates.row.check.title": "Nach Updates suchen",
@@ -597,9 +617,11 @@ export const dict = {
"sound.option.yup05": "Ja 05",
"sound.option.yup06": "Ja 06",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description": "Systembenachrichtigung anzeigen, wenn der Agent fertig ist oder Aufmerksamkeit benötigt",
"settings.general.notifications.agent.description":
"Systembenachrichtigung anzeigen, wenn der Agent fertig ist oder Aufmerksamkeit benötigt",
"settings.general.notifications.permissions.title": "Berechtigungen",
"settings.general.notifications.permissions.description": "Systembenachrichtigung anzeigen, wenn eine Berechtigung erforderlich ist",
"settings.general.notifications.permissions.description":
"Systembenachrichtigung anzeigen, wenn eine Berechtigung erforderlich ist",
"settings.general.notifications.errors.title": "Fehler",
"settings.general.notifications.errors.description": "Systembenachrichtigung anzeigen, wenn ein Fehler auftritt",
"settings.general.sounds.agent.title": "Agent",
@@ -651,7 +673,8 @@ export const dict = {
"settings.permissions.tool.read.title": "Lesen",
"settings.permissions.tool.read.description": "Lesen einer Datei (stimmt mit dem Dateipfad überein)",
"settings.permissions.tool.edit.title": "Bearbeiten",
"settings.permissions.tool.edit.description": "Dateien ändern, einschließlich Bearbeitungen, Schreibvorgängen, Patches und Mehrfachbearbeitungen",
"settings.permissions.tool.edit.description":
"Dateien ändern, einschließlich Bearbeitungen, Schreibvorgängen, Patches und Mehrfachbearbeitungen",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Dateien mithilfe von Glob-Mustern abgleichen",
"settings.permissions.tool.grep.title": "Grep",
@@ -682,7 +705,7 @@ export const dict = {
"settings.permissions.tool.doom_loop.description": "Wiederholte Tool-Aufrufe mit identischer Eingabe erkennen",
"session.delete.failed.title": "Sitzung konnte nicht gelöscht werden",
"session.delete.title": "Sitzung löschen",
"session.delete.confirm": "Sitzung \"{{name}}\" löschen?",
"session.delete.confirm": 'Sitzung "{{name}}" löschen?',
"session.delete.button": "Sitzung löschen",
"workspace.new": "Neuer Arbeitsbereich",
"workspace.type.local": "lokal",
@@ -700,13 +723,13 @@ export const dict = {
"workspace.status.clean": "Keine nicht zusammengeführten Änderungen erkannt.",
"workspace.status.dirty": "Nicht zusammengeführte Änderungen in diesem Arbeitsbereich erkannt.",
"workspace.delete.title": "Arbeitsbereich löschen",
"workspace.delete.confirm": "Arbeitsbereich \"{{name}}\" löschen?",
"workspace.delete.confirm": 'Arbeitsbereich "{{name}}" löschen?',
"workspace.delete.button": "Arbeitsbereich löschen",
"workspace.reset.title": "Arbeitsbereich zurücksetzen",
"workspace.reset.confirm": "Arbeitsbereich \"{{name}}\" zurücksetzen?",
"workspace.reset.confirm": 'Arbeitsbereich "{{name}}" zurücksetzen?',
"workspace.reset.button": "Arbeitsbereich zurücksetzen",
"workspace.reset.archived.none": "Keine aktiven Sitzungen werden archiviert.",
"workspace.reset.archived.one": "1 Sitzung wird archiviert.",
"workspace.reset.archived.many": "{{count}} Sitzungen werden archiviert.",
"workspace.reset.note": "Dadurch wird der Arbeitsbereich auf den Standard-Branch zurückgesetzt."
"workspace.reset.note": "Dadurch wird der Arbeitsbereich auf den Standard-Branch zurückgesetzt.",
} satisfies Partial<Record<Keys, string>>

View File

@@ -110,25 +110,30 @@ export const dict = {
"provider.connect.status.inProgress": "Autoryzacja w toku...",
"provider.connect.status.waiting": "Oczekiwanie na autoryzację...",
"provider.connect.status.failed": "Autoryzacja nie powiodła się: {{error}}",
"provider.connect.apiKey.description": "Wprowadź swój klucz API {{provider}}, aby połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.apiKey.description":
"Wprowadź swój klucz API {{provider}}, aby połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.apiKey.label": "Klucz API {{provider}}",
"provider.connect.apiKey.placeholder": "Klucz API",
"provider.connect.apiKey.required": "Klucz API jest wymagany",
"provider.connect.opencodeZen.line1": "OpenCode Zen daje dostęp do wybranego zestawu niezawodnych, zoptymalizowanych modeli dla agentów kodujących.",
"provider.connect.opencodeZen.line2": "Z jednym kluczem API uzyskasz dostęp do modeli takich jak Claude, GPT, Gemini, GLM i więcej.",
"provider.connect.opencodeZen.line1":
"OpenCode Zen daje dostęp do wybranego zestawu niezawodnych, zoptymalizowanych modeli dla agentów kodujących.",
"provider.connect.opencodeZen.line2":
"Z jednym kluczem API uzyskasz dostęp do modeli takich jak Claude, GPT, Gemini, GLM i więcej.",
"provider.connect.opencodeZen.visit.prefix": "Odwiedź ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": ", aby odebrać swój klucz API.",
"provider.connect.oauth.code.visit.prefix": "Odwiedź ",
"provider.connect.oauth.code.visit.link": "ten link",
"provider.connect.oauth.code.visit.suffix": ", aby odebrać kod autoryzacyjny, połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.oauth.code.visit.suffix":
", aby odebrać kod autoryzacyjny, połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.oauth.code.label": "Kod autoryzacyjny {{method}}",
"provider.connect.oauth.code.placeholder": "Kod autoryzacyjny",
"provider.connect.oauth.code.required": "Kod autoryzacyjny jest wymagany",
"provider.connect.oauth.code.invalid": "Nieprawidłowy kod autoryzacyjny",
"provider.connect.oauth.auto.visit.prefix": "Odwiedź ",
"provider.connect.oauth.auto.visit.link": "ten link",
"provider.connect.oauth.auto.visit.suffix": " i wprowadź poniższy kod, aby połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.oauth.auto.visit.suffix":
" i wprowadź poniższy kod, aby połączyć konto i używać modeli {{provider}} w OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Kod potwierdzający",
"provider.connect.toast.connected.title": "Połączono {{provider}}",
"provider.connect.toast.connected.description": "Modele {{provider}} są teraz dostępne do użycia.",
@@ -145,7 +150,8 @@ export const dict = {
"provider.custom.field.baseURL.placeholder": "https://api.mojdostawca.com/v1",
"provider.custom.field.apiKey.label": "Klucz API",
"provider.custom.field.apiKey.placeholder": "Klucz API",
"provider.custom.field.apiKey.description": "Opcjonalne. Pozostaw puste, jeśli zarządzasz autoryzacją przez nagłówki.",
"provider.custom.field.apiKey.description":
"Opcjonalne. Pozostaw puste, jeśli zarządzasz autoryzacją przez nagłówki.",
"provider.custom.models.label": "Modele",
"provider.custom.models.id.label": "ID",
"provider.custom.models.id.placeholder": "model-id",
@@ -278,7 +284,8 @@ export const dict = {
"dialog.server.add.checking": "Sprawdzanie...",
"dialog.server.add.button": "Dodaj serwer",
"dialog.server.default.title": "Domyślny serwer",
"dialog.server.default.description": "Połącz z tym serwerem przy uruchomieniu aplikacji zamiast uruchamiać lokalny serwer. Wymaga restartu.",
"dialog.server.default.description":
"Połącz z tym serwerem przy uruchomieniu aplikacji zamiast uruchamiać lokalny serwer. Wymaga restartu.",
"dialog.server.default.none": "Nie wybrano serwera",
"dialog.server.default.set": "Ustaw bieżący serwer jako domyślny",
"dialog.server.default.clear": "Wyczyść",
@@ -388,7 +395,8 @@ export const dict = {
"error.page.report.prefix": "Proszę zgłosić ten błąd do zespołu OpenCode",
"error.page.report.discord": "na Discordzie",
"error.page.version": "Wersja: {{version}}",
"error.dev.rootNotFound": "Nie znaleziono elementu głównego. Czy zapomniałeś dodać go do swojego index.html? A może atrybut id został błędnie wpisany?",
"error.dev.rootNotFound":
"Nie znaleziono elementu głównego. Czy zapomniałeś dodać go do swojego index.html? A może atrybut id został błędnie wpisany?",
"error.globalSync.connectFailed": "Nie można połączyć się z serwerem. Czy serwer działa pod adresem `{{url}}`?",
"directory.error.invalidUrl": "Nieprawidłowy katalog w URL.",
"error.chain.unknown": "Nieznany błąd",
@@ -402,10 +410,12 @@ export const dict = {
"error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli",
"error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.',
"error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}",
"error.chain.providerInitFailed": 'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
"error.chain.providerInitFailed":
'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
"error.chain.configJsonInvalid": "Plik konfiguracyjny w {{path}} nie jest poprawnym JSON(C)",
"error.chain.configJsonInvalidWithMessage": "Plik konfiguracyjny w {{path}} nie jest poprawnym JSON(C): {{message}}",
"error.chain.configDirectoryTypo": 'Katalog "{{dir}}" w {{path}} jest nieprawidłowy. Zmień nazwę katalogu na "{{suggestion}}" lub usuń go. To częsta literówka.',
"error.chain.configDirectoryTypo":
'Katalog "{{dir}}" w {{path}} jest nieprawidłowy. Zmień nazwę katalogu na "{{suggestion}}" lub usuń go. To częsta literówka.',
"error.chain.configFrontmatterError": "Nie udało się przetworzyć frontmatter w {{path}}:\n{{message}}",
"error.chain.configInvalid": "Plik konfiguracyjny w {{path}} jest nieprawidłowy",
"error.chain.configInvalidWithMessage": "Plik konfiguracyjny w {{path}} jest nieprawidłowy: {{message}}",
@@ -458,8 +468,10 @@ export const dict = {
"status.popover.tab.plugins": "Wtyczki",
"status.popover.action.manageServers": "Zarządzaj serwerami",
"session.share.popover.title": "Opublikuj w sieci",
"session.share.popover.description.shared": "Ta sesja jest publiczna w sieci. Jest dostępna dla każdego, kto posiada link.",
"session.share.popover.description.unshared": "Udostępnij sesję publicznie w sieci. Będzie dostępna dla każdego, kto posiada link.",
"session.share.popover.description.shared":
"Ta sesja jest publiczna w sieci. Jest dostępna dla każdego, kto posiada link.",
"session.share.popover.description.unshared":
"Udostępnij sesję publicznie w sieci. Będzie dostępna dla każdego, kto posiada link.",
"session.share.action.share": "Udostępnij",
"session.share.action.publish": "Opublikuj",
"session.share.action.publishing": "Publikowanie...",
@@ -476,7 +488,8 @@ export const dict = {
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Zamknij terminal",
"terminal.connectionLost.title": "Utracono połączenie",
"terminal.connectionLost.description": "Połączenie z terminalem zostało przerwane. Może się to zdarzyć przy restarcie serwera.",
"terminal.connectionLost.description":
"Połączenie z terminalem zostało przerwane. Może się to zdarzyć przy restarcie serwera.",
"common.closeTab": "Zamknij kartę",
"common.dismiss": "Odrzuć",
"common.requestFailed": "Żądanie nie powiodło się",
@@ -524,7 +537,8 @@ export const dict = {
"settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
"settings.general.row.wayland.tooltip": "Na Linuxie z monitorami o różnym odświeżaniu, natywny Wayland może być bardziej stabilny.",
"settings.general.row.wayland.tooltip":
"Na Linuxie z monitorami o różnym odświeżaniu, natywny Wayland może być bardziej stabilny.",
"settings.general.row.releaseNotes.title": "Informacje o wydaniu",
"settings.general.row.releaseNotes.description": 'Pokazuj wyskakujące okna "Co nowego" po aktualizacjach',
"settings.updates.row.startup.title": "Sprawdzaj aktualizacje przy uruchomieniu",
@@ -593,9 +607,11 @@ export const dict = {
"sound.option.yup05": "Yup 05",
"sound.option.yup06": "Yup 06",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description": "Pokaż powiadomienie systemowe, gdy agent zakończy pracę lub wymaga uwagi",
"settings.general.notifications.agent.description":
"Pokaż powiadomienie systemowe, gdy agent zakończy pracę lub wymaga uwagi",
"settings.general.notifications.permissions.title": "Uprawnienia",
"settings.general.notifications.permissions.description": "Pokaż powiadomienie systemowe, gdy wymagane jest uprawnienie",
"settings.general.notifications.permissions.description":
"Pokaż powiadomienie systemowe, gdy wymagane jest uprawnienie",
"settings.general.notifications.errors.title": "Błędy",
"settings.general.notifications.errors.description": "Pokaż powiadomienie systemowe, gdy wystąpi błąd",
"settings.general.sounds.agent.title": "Agent",

View File

@@ -147,7 +147,8 @@ export const dict = {
"provider.connect.status.inProgress": "正在授权...",
"provider.connect.status.waiting": "等待授权...",
"provider.connect.status.failed": "授权失败:{{error}}",
"provider.connect.apiKey.description": "输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。",
"provider.connect.apiKey.description":
"输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。",
"provider.connect.apiKey.label": "{{provider}} API 密钥",
"provider.connect.apiKey.placeholder": "API 密钥",
"provider.connect.apiKey.required": "API 密钥为必填项",
@@ -242,7 +243,7 @@ export const dict = {
"common.attachment": "附件",
"prompt.placeholder.shell": "输入 shell 命令...",
"prompt.placeholder.normal": "随便问点什么... \"{{example}}\"",
"prompt.placeholder.normal": '随便问点什么... "{{example}}"',
"prompt.placeholder.summarizeComments": "总结评论…",
"prompt.placeholder.summarizeComment": "总结该评论…",
"prompt.mode.shell": "Shell",
@@ -456,12 +457,13 @@ export const dict = {
"error.chain.didYouMean": "你是不是想输入:{{suggestions}}",
"error.chain.modelNotFound": "未找到模型:{{provider}}/{{model}}",
"error.chain.checkConfig": "请检查你的配置 (opencode.json) 中的 provider/model 名称",
"error.chain.mcpFailed": "MCP 服务器 \"{{name}}\" 启动失败。注意: OpenCode 暂不支持 MCP 认证。",
"error.chain.mcpFailed": 'MCP 服务器 "{{name}}" 启动失败。注意: OpenCode 暂不支持 MCP 认证。',
"error.chain.providerAuthFailed": "提供商认证失败({{provider}}{{message}}",
"error.chain.providerInitFailed": "无法初始化提供商 \"{{provider}}\"。请检查凭据和配置。",
"error.chain.providerInitFailed": '无法初始化提供商 "{{provider}}"。请检查凭据和配置。',
"error.chain.configJsonInvalid": "配置文件 {{path}} 不是有效的 JSON(C)",
"error.chain.configJsonInvalidWithMessage": "配置文件 {{path}} 不是有效的 JSON(C){{message}}",
"error.chain.configDirectoryTypo": "{{path}} 中的目录 \"{{dir}}\" 无效。请将目录重命名为 \"{{suggestion}}\" 或移除它。这是一个常见拼写错误。",
"error.chain.configDirectoryTypo":
'{{path}} 中的目录 "{{dir}}" 无效。请将目录重命名为 "{{suggestion}}" 或移除它。这是一个常见拼写错误。',
"error.chain.configFrontmatterError": "无法解析 {{path}} 中的 frontmatter\n{{message}}",
"error.chain.configInvalid": "配置文件 {{path}} 无效",
"error.chain.configInvalidWithMessage": "配置文件 {{path}} 无效:{{message}}",
@@ -761,7 +763,7 @@ export const dict = {
"session.delete.failed.title": "删除会话失败",
"session.delete.title": "删除会话",
"session.delete.confirm": "删除会话 \"{{name}}\"",
"session.delete.confirm": '删除会话 "{{name}}"',
"session.delete.button": "删除会话",
"workspace.new": "新建工作区",
@@ -780,10 +782,10 @@ export const dict = {
"workspace.status.clean": "未检测到未合并的更改。",
"workspace.status.dirty": "检测到未合并的更改。",
"workspace.delete.title": "删除工作区",
"workspace.delete.confirm": "删除工作区 \"{{name}}\"",
"workspace.delete.confirm": '删除工作区 "{{name}}"',
"workspace.delete.button": "删除工作区",
"workspace.reset.title": "重置工作区",
"workspace.reset.confirm": "重置工作区 \"{{name}}\"",
"workspace.reset.confirm": '重置工作区 "{{name}}"',
"workspace.reset.button": "重置工作区",
"workspace.reset.archived.none": "不会归档任何活跃会话。",
"workspace.reset.archived.one": "将归档 1 个会话。",

View File

@@ -1203,6 +1203,16 @@ export default function Layout(props: ParentProps) {
if (!result) return
globalSync.set(
"project",
produce((draft) => {
const project = draft.find((item) => item.worktree === root)
if (!project) return
project.sandboxes = (project.sandboxes ?? []).filter((sandbox) => sandbox !== directory)
}),
)
setStore("workspaceOrder", root, (order) => (order ?? []).filter((workspace) => workspace !== directory))
layout.projects.close(directory)
layout.projects.open(root)
@@ -1230,6 +1240,7 @@ export default function Layout(props: ParentProps) {
clearWorkspaceTerminals(
directory,
sessions.map((s) => s.id),
platform,
)
await globalSDK.client.instance.dispose({ directory }).catch(() => undefined)

View File

@@ -1,4 +1,4 @@
import { usePlatform } from "@/context/platform"
import { Platform, usePlatform } from "@/context/platform"
import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage"
import { checksum } from "@opencode-ai/util/encode"
import { createResource, type Accessor } from "solid-js"
@@ -318,9 +318,8 @@ export const Persist = {
},
}
export function removePersisted(target: { storage?: string; key: string }) {
const platform = usePlatform()
const isDesktop = platform.platform === "desktop" && !!platform.storage
export function removePersisted(target: { storage?: string; key: string }, platform?: Platform) {
const isDesktop = platform?.platform === "desktop" && !!platform.storage
if (isDesktop) {
return platform.storage?.(target.storage)?.removeItem(target.key)

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

View File

@@ -0,0 +1,18 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(30, 0)">
<g clip-path="url(#clip0_1401_86283)">
<mask id="mask0_1401_86283" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
<path d="M240 0H0V300H240V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_1401_86283)">
<path d="M180 240H60V120H180V240Z" fill="#4B4646"/>
<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#F1ECEC"/>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_1401_86283">
<rect width="240" height="300" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

View File

@@ -0,0 +1,18 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(30, 0)">
<g clip-path="url(#clip0_1401_86274)">
<mask id="mask0_1401_86274" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
<path d="M240 0H0V300H240V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_1401_86274)">
<path d="M180 240H60V120H180V240Z" fill="#CFCECD"/>
<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#211E1E"/>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_1401_86274">
<rect width="240" height="300" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -7,18 +7,24 @@ import { useI18n } from "~/context/i18n"
import { LocaleLinks } from "~/component/locale-links"
import previewLogoLight from "../../asset/brand/preview-opencode-logo-light.png"
import previewLogoDark from "../../asset/brand/preview-opencode-logo-dark.png"
import previewLogoLightSquare from "../../asset/brand/preview-opencode-logo-light-square.png"
import previewLogoDarkSquare from "../../asset/brand/preview-opencode-logo-dark-square.png"
import previewWordmarkLight from "../../asset/brand/preview-opencode-wordmark-light.png"
import previewWordmarkDark from "../../asset/brand/preview-opencode-wordmark-dark.png"
import previewWordmarkSimpleLight from "../../asset/brand/preview-opencode-wordmark-simple-light.png"
import previewWordmarkSimpleDark from "../../asset/brand/preview-opencode-wordmark-simple-dark.png"
import logoLightPng from "../../asset/brand/opencode-logo-light.png"
import logoDarkPng from "../../asset/brand/opencode-logo-dark.png"
import logoLightSquarePng from "../../asset/brand/opencode-logo-light-square.png"
import logoDarkSquarePng from "../../asset/brand/opencode-logo-dark-square.png"
import wordmarkLightPng from "../../asset/brand/opencode-wordmark-light.png"
import wordmarkDarkPng from "../../asset/brand/opencode-wordmark-dark.png"
import wordmarkSimpleLightPng from "../../asset/brand/opencode-wordmark-simple-light.png"
import wordmarkSimpleDarkPng from "../../asset/brand/opencode-wordmark-simple-dark.png"
import logoLightSvg from "../../asset/brand/opencode-logo-light.svg"
import logoDarkSvg from "../../asset/brand/opencode-logo-dark.svg"
import logoLightSquareSvg from "../../asset/brand/opencode-logo-light-square.svg"
import logoDarkSquareSvg from "../../asset/brand/opencode-logo-dark-square.svg"
import wordmarkLightSvg from "../../asset/brand/opencode-wordmark-light.svg"
import wordmarkDarkSvg from "../../asset/brand/opencode-wordmark-dark.svg"
import wordmarkSimpleLightSvg from "../../asset/brand/opencode-wordmark-simple-light.svg"
@@ -135,6 +141,60 @@ export default function Brand() {
</button>
</div>
</div>
<div>
<img src={previewLogoLightSquare} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(logoLightSquarePng, "opencode-logo-light-square.png")}>
PNG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
</button>
<button onClick={() => downloadFile(logoLightSquareSvg, "opencode-logo-light-square.svg")}>
SVG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
</button>
</div>
</div>
<div>
<img src={previewLogoDarkSquare} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(logoDarkSquarePng, "opencode-logo-dark-square.png")}>
PNG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
</button>
<button onClick={() => downloadFile(logoDarkSquareSvg, "opencode-logo-dark-square.svg")}>
SVG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
</button>
</div>
</div>
<div>
<img src={previewWordmarkLight} alt="OpenCode brand guidelines" />
<div data-component="actions">

View File

@@ -2,9 +2,9 @@ Performs exact string replacements in files.
Usage:
- You must use your `Read` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the oldString or newString.
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: line number + colon + space (e.g., `1: `). Everything after that space is the actual file content to match. Never include any part of the line number prefix in the oldString or newString.
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
- The edit will FAIL if `oldString` is not found in the file with an error "oldString not found in content".
- The edit will FAIL if `oldString` is found multiple times in the file with an error "oldString found multiple times and requires more code context to uniquely identify the intended match". Either provide a larger string with more surrounding context to make it unique or use `replaceAll` to change every instance of `oldString`.
- The edit will FAIL if `oldString` is found multiple times in the file with an error "Found multiple matches for oldString. Provide more surrounding lines in oldString to identify the correct match." Either provide a larger string with more surrounding context to make it unique or use `replaceAll` to change every instance of `oldString`.
- Use `replaceAll` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.

View File

@@ -17,9 +17,9 @@ const MAX_BYTES = 50 * 1024
export const ReadTool = Tool.define("read", {
description: DESCRIPTION,
parameters: z.object({
filePath: z.string().describe("The path to the file to read"),
offset: z.coerce.number().describe("The line number to start reading from (0-based)").optional(),
limit: z.coerce.number().describe("The number of lines to read (defaults to 2000)").optional(),
filePath: z.string().describe("The absolute path to the file or directory to read"),
offset: z.coerce.number().describe("The 0-based line offset to start reading from").optional(),
limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(),
}),
async execute(params, ctx) {
let filepath = params.filePath
@@ -28,8 +28,12 @@ export const ReadTool = Tool.define("read", {
}
const title = path.relative(Instance.worktree, filepath)
const file = Bun.file(filepath)
const stat = await file.stat().catch(() => undefined)
await assertExternalDirectory(ctx, filepath, {
bypass: Boolean(ctx.extra?.["bypassCwdCheck"]),
kind: stat?.isDirectory() ? "directory" : "file",
})
await ctx.ask({
@@ -39,8 +43,7 @@ export const ReadTool = Tool.define("read", {
metadata: {},
})
const file = Bun.file(filepath)
if (!(await file.exists())) {
if (!stat) {
const dir = path.dirname(filepath)
const base = path.basename(filepath)
@@ -60,6 +63,47 @@ export const ReadTool = Tool.define("read", {
throw new Error(`File not found: ${filepath}`)
}
if (stat.isDirectory()) {
const dirents = await fs.promises.readdir(filepath, { withFileTypes: true })
const entries = await Promise.all(
dirents.map(async (dirent) => {
if (dirent.isDirectory()) return dirent.name + "/"
if (dirent.isSymbolicLink()) {
const target = await fs.promises.stat(path.join(filepath, dirent.name)).catch(() => undefined)
if (target?.isDirectory()) return dirent.name + "/"
}
return dirent.name
}),
)
entries.sort((a, b) => a.localeCompare(b))
const limit = params.limit ?? DEFAULT_READ_LIMIT
const offset = params.offset || 0
const sliced = entries.slice(offset, offset + limit)
const truncated = offset + sliced.length < entries.length
const output = [
`<path>${filepath}</path>`,
`<type>directory</type>`,
`<entries>`,
sliced.join("\n"),
truncated
? `\n(Showing ${sliced.length} of ${entries.length} entries. Use 'offset' parameter to read beyond entry ${offset + sliced.length})`
: `\n(${entries.length} entries)`,
`</entries>`,
].join("\n")
return {
title,
output,
metadata: {
preview: sliced.slice(0, 20).join("\n"),
truncated,
loaded: [] as string[],
},
}
}
const instructions = await InstructionPrompt.resolve(ctx.messages, filepath, ctx.messageID)
// Exclude SVG (XML-based) and vnd.fastbidsheet (.fbs extension, commonly FlatBuffers schema files)
@@ -75,7 +119,7 @@ export const ReadTool = Tool.define("read", {
metadata: {
preview: msg,
truncated: false,
...(instructions.length > 0 && { loaded: instructions.map((i) => i.filepath) }),
loaded: instructions.map((i) => i.filepath),
},
attachments: [
{
@@ -112,11 +156,11 @@ export const ReadTool = Tool.define("read", {
}
const content = raw.map((line, index) => {
return `${(index + offset + 1).toString().padStart(5, "0")}| ${line}`
return `${index + offset + 1}: ${line}`
})
const preview = raw.slice(0, 20).join("\n")
let output = "<file>\n"
let output = [`<path>${filepath}</path>`, `<type>file</type>`, "<content>"].join("\n")
output += content.join("\n")
const totalLines = lines.length
@@ -131,7 +175,7 @@ export const ReadTool = Tool.define("read", {
} else {
output += `\n\n(End of file - total ${totalLines} lines)`
}
output += "\n</file>"
output += "\n</content>"
// just warms the lsp client
LSP.touchFile(filepath, false)
@@ -147,7 +191,7 @@ export const ReadTool = Tool.define("read", {
metadata: {
preview,
truncated,
...(instructions.length > 0 && { loaded: instructions.map((i) => i.filepath) }),
loaded: instructions.map((i) => i.filepath),
},
}
},

View File

@@ -1,12 +1,13 @@
Reads a file from the local filesystem. You can access any file directly by using this tool.
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
Read a file or directory from the local filesystem. If the path does not exist, an error is returned.
Usage:
- The filePath parameter must be an absolute path, not a relative path
- By default, it reads up to 2000 lines starting from the beginning of the file
- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
- Any lines longer than 2000 characters will be truncated
- Results are returned using cat -n format, with line numbers starting at 1
- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
- You can read image files using this tool.
- The filePath parameter should be an absolute path.
- By default, this tool returns up to 2000 lines from the start of the file.
- To read later sections, call this tool again with a larger offset.
- Use the grep tool to find specific content in large files or files with long lines.
- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.
- Contents are returned with each line prefixed by its line number as `<line>: <content>`. For example, if a file has contents "foo\n", you will receive "1: foo\n". For directories, entries are returned one per line (without line numbers) with a trailing `/` for subdirectories.
- Any line longer than 2000 characters is truncated.
- Call this tool in parallel when you know there are multiple files you want to read.
- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.
- This tool can read image files and PDFs and return them as file attachments.

View File

@@ -78,6 +78,32 @@ describe("tool.read external_directory permission", () => {
})
})
test("asks for directory-scoped external_directory permission when reading external directory", async () => {
await using outerTmp = await tmpdir({
init: async (dir) => {
await Bun.write(path.join(dir, "external", "a.txt"), "a")
},
})
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const read = await ReadTool.init()
const requests: Array<Omit<PermissionNext.Request, "id" | "sessionID" | "tool">> = []
const testCtx = {
...ctx,
ask: async (req: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">) => {
requests.push(req)
},
}
await read.execute({ filePath: path.join(outerTmp.path, "external") }, testCtx)
const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined()
expect(extDirReq!.patterns).toContain(path.join(outerTmp.path, "external", "*"))
},
})
})
test("asks for external_directory permission when reading relative path outside project", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
@@ -249,6 +275,25 @@ describe("tool.read truncation", () => {
})
})
test("does not mark final directory page as truncated", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Promise.all(
Array.from({ length: 10 }, (_, i) => Bun.write(path.join(dir, "dir", `file-${i}.txt`), `line${i}`)),
)
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const read = await ReadTool.init()
const result = await read.execute({ filePath: path.join(tmp.path, "dir"), offset: 5, limit: 5 }, ctx)
expect(result.metadata.truncated).toBe(false)
expect(result.output).not.toContain("Showing 5 of 10 entries")
},
})
})
test("truncates long lines", async () => {
await using tmp = await tmpdir({
init: async (dir) => {

View File

@@ -1,20 +1,21 @@
export const dict = {
"ui.sessionReview.title": "تغييرات الجلسة",
"ui.sessionReview.title.lastTurn": "تغييرات آخر دور",
"ui.sessionReview.diffStyle.unified": "موجد",
"ui.sessionReview.diffStyle.unified": "موحد",
"ui.sessionReview.diffStyle.split": "منقسم",
"ui.sessionReview.expandAll": "توسيع الكل",
"ui.sessionReview.collapseAll": "طي الكل",
"ui.sessionReview.change.added": "مضاف",
"ui.sessionReview.change.removed": "محذوف",
"ui.sessionReview.change.modified": "معدل",
"ui.lineComment.label.prefix": "تعليق على ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "جارٍ التعليق على ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "أضف تعليقًا",
"ui.lineComment.submit": "تعليق",
"ui.sessionTurn.steps.show": "إظهار الخطوات",
"ui.sessionTurn.steps.hide": "إخفاء الخطوات",
"ui.sessionTurn.summary.response": "استجابة",

View File

@@ -5,16 +5,17 @@ export const dict = {
"ui.sessionReview.diffStyle.split": "Dividido",
"ui.sessionReview.expandAll": "Expandir tudo",
"ui.sessionReview.collapseAll": "Recolher tudo",
"ui.sessionReview.change.added": "Adicionado",
"ui.sessionReview.change.removed": "Removido",
"ui.sessionReview.change.modified": "Modificado",
"ui.lineComment.label.prefix": "Comentar em ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Comentando em ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "Adicionar comentário",
"ui.lineComment.submit": "Comentar",
"ui.sessionTurn.steps.show": "Mostrar passos",
"ui.sessionTurn.steps.hide": "Ocultar passos",
"ui.sessionTurn.summary.response": "Resposta",
@@ -94,7 +95,7 @@ export const dict = {
"ui.patch.action.deleted": "Excluído",
"ui.patch.action.created": "Criado",
"ui.patch.action.moved": "Movido",
"ui.patch.action.patched": "Aplicado patch",
"ui.patch.action.patched": "Patch aplicado",
"ui.question.subtitle.answered": "{{count}} respondidas",
"ui.question.answer.none": "(sem resposta)",

View File

@@ -63,8 +63,8 @@ export const dict = {
"ui.tool.webfetch": "Webhentning",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "To-dos",
"ui.tool.todos.read": "Læs to-dos",
"ui.tool.todos": "Opgaver",
"ui.tool.todos.read": "Læs opgaver",
"ui.tool.questions": "Spørgsmål",
"ui.tool.agent": "{{type}} Agent",
@@ -94,7 +94,7 @@ export const dict = {
"ui.patch.action.deleted": "Slettet",
"ui.patch.action.created": "Oprettet",
"ui.patch.action.moved": "Flyttet",
"ui.patch.action.patched": "Patched",
"ui.patch.action.patched": "Patchet",
"ui.question.subtitle.answered": "{{count}} besvaret",
"ui.question.answer.none": "(intet svar)",

View File

@@ -5,16 +5,17 @@ export const dict = {
"ui.sessionReview.diffStyle.split": "Dividido",
"ui.sessionReview.expandAll": "Expandir todo",
"ui.sessionReview.collapseAll": "Colapsar todo",
"ui.sessionReview.change.added": "Añadido",
"ui.sessionReview.change.removed": "Eliminado",
"ui.sessionReview.change.modified": "Modificado",
"ui.lineComment.label.prefix": "Comentar en ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Comentando en ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "Añadir comentario",
"ui.lineComment.submit": "Comentar",
"ui.sessionTurn.steps.show": "Mostrar pasos",
"ui.sessionTurn.steps.hide": "Ocultar pasos",
"ui.sessionTurn.summary.response": "Respuesta",

View File

@@ -5,16 +5,17 @@ export const dict = {
"ui.sessionReview.diffStyle.split": "Divisé",
"ui.sessionReview.expandAll": "Tout développer",
"ui.sessionReview.collapseAll": "Tout réduire",
"ui.sessionReview.change.added": "Ajouté",
"ui.sessionReview.change.removed": "Supprimé",
"ui.sessionReview.change.modified": "Modifié",
"ui.lineComment.label.prefix": "Commenter sur ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Commentaire sur ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "Ajouter un commentaire",
"ui.lineComment.submit": "Commenter",
"ui.sessionTurn.steps.show": "Afficher les étapes",
"ui.sessionTurn.steps.hide": "Masquer les étapes",
"ui.sessionTurn.summary.response": "Réponse",

View File

@@ -5,16 +5,17 @@ export const dict = {
"ui.sessionReview.diffStyle.split": "분할 보기",
"ui.sessionReview.expandAll": "모두 펼치기",
"ui.sessionReview.collapseAll": "모두 접기",
"ui.sessionReview.change.added": "추가됨",
"ui.sessionReview.change.removed": "삭제됨",
"ui.sessionReview.change.modified": "수정됨",
"ui.lineComment.label.prefix": "",
"ui.lineComment.label.suffix": "에 댓글 달기",
"ui.lineComment.editorLabel.prefix": "",
"ui.lineComment.editorLabel.suffix": "에 댓글 작성 중",
"ui.lineComment.placeholder": "댓글 추가",
"ui.lineComment.submit": "댓글",
"ui.sessionTurn.steps.show": "단계 표시",
"ui.sessionTurn.steps.hide": "단계 숨기기",
"ui.sessionTurn.summary.response": "응답",

View File

@@ -8,16 +8,17 @@ export const dict: Record<Keys, string> = {
"ui.sessionReview.diffStyle.split": "Delt",
"ui.sessionReview.expandAll": "Utvid alle",
"ui.sessionReview.collapseAll": "Fold sammen alle",
"ui.sessionReview.change.added": "Lagt til",
"ui.sessionReview.change.removed": "Fjernet",
"ui.sessionReview.change.modified": "Endret",
"ui.lineComment.label.prefix": "Kommenter på ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Kommenterer på ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "Legg til kommentar",
"ui.lineComment.submit": "Kommenter",
"ui.sessionTurn.steps.show": "Vis trinn",
"ui.sessionTurn.steps.hide": "Skjul trinn",
"ui.sessionTurn.summary.response": "Svar",

View File

@@ -57,6 +57,7 @@ export const dict = {
"ui.imagePreview.alt": "ตัวอย่างรูปภาพ",
"ui.tool.read": "อ่าน",
"ui.tool.loaded": "โหลดแล้ว",
"ui.tool.list": "รายการ",
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",

View File

@@ -9,16 +9,17 @@ export const dict = {
"ui.sessionReview.diffStyle.split": "拆分",
"ui.sessionReview.expandAll": "全部展开",
"ui.sessionReview.collapseAll": "全部收起",
"ui.sessionReview.change.added": "已添加",
"ui.sessionReview.change.removed": "已移除",
"ui.sessionReview.change.modified": "已修改",
"ui.lineComment.label.prefix": "评论 ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "正在评论 ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "添加评论",
"ui.lineComment.submit": "评论",
"ui.sessionTurn.steps.show": "显示步骤",
"ui.sessionTurn.steps.hide": "隐藏步骤",
"ui.sessionTurn.summary.response": "回复",
@@ -92,7 +93,7 @@ export const dict = {
"ui.message.expand": "展开消息",
"ui.message.collapse": "收起消息",
"ui.message.copy": "复制",
"ui.message.copied": "已复制",
"ui.message.copied": "已复制",
"ui.message.attachment.alt": "附件",
"ui.patch.action.deleted": "已删除",

View File

@@ -9,16 +9,17 @@ export const dict = {
"ui.sessionReview.diffStyle.split": "拆分",
"ui.sessionReview.expandAll": "全部展開",
"ui.sessionReview.collapseAll": "全部收合",
"ui.sessionReview.change.added": "已新增",
"ui.sessionReview.change.removed": "已移除",
"ui.sessionReview.change.modified": "已修改",
"ui.lineComment.label.prefix": "評論 ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "正在評論 ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "新增評論",
"ui.lineComment.submit": "評論",
"ui.sessionTurn.steps.show": "顯示步驟",
"ui.sessionTurn.steps.hide": "隱藏步驟",
"ui.sessionTurn.summary.response": "回覆",
@@ -92,7 +93,7 @@ export const dict = {
"ui.message.expand": "展開訊息",
"ui.message.collapse": "收合訊息",
"ui.message.copy": "複製",
"ui.message.copied": "已複製",
"ui.message.copied": "已複製",
"ui.message.attachment.alt": "附件",
"ui.patch.action.deleted": "已刪除",

234
square-logos.patch Normal file
View File

@@ -0,0 +1,234 @@
From 90904222b6f8c86a6d0a8ebed9661950f632a4e8 Mon Sep 17 00:00:00 2001
From: OpenCode Bot <opencode@sst.dev>
Date: Wed, 11 Feb 2026 18:44:27 +0000
Subject: [PATCH] add square logo variants to brand page
---
.../asset/brand/opencode-logo-dark-square.png | Bin 0 -> 697 bytes
.../asset/brand/opencode-logo-dark-square.svg | 18 ++++++
.../brand/opencode-logo-light-square.png | Bin 0 -> 697 bytes
.../brand/opencode-logo-light-square.svg | 18 ++++++
.../preview-opencode-logo-dark-square.png | Bin 0 -> 1477 bytes
.../preview-opencode-logo-light-square.png | Bin 0 -> 1467 bytes
.../console/app/src/routes/brand/index.tsx | 60 ++++++++++++++++++
7 files changed, 96 insertions(+)
create mode 100644 packages/console/app/src/asset/brand/opencode-logo-dark-square.png
create mode 100644 packages/console/app/src/asset/brand/opencode-logo-dark-square.svg
create mode 100644 packages/console/app/src/asset/brand/opencode-logo-light-square.png
create mode 100644 packages/console/app/src/asset/brand/opencode-logo-light-square.svg
create mode 100644 packages/console/app/src/asset/brand/preview-opencode-logo-dark-square.png
create mode 100644 packages/console/app/src/asset/brand/preview-opencode-logo-light-square.png
diff --git a/packages/console/app/src/asset/brand/opencode-logo-dark-square.png b/packages/console/app/src/asset/brand/opencode-logo-dark-square.png
new file mode 100644
index 0000000000000000000000000000000000000000..673c7e3a20f917fae56719ed1c35b4614ecd5f53
GIT binary patch
literal 697
zcmeAS@N?(olHy`uVBq!ia0y~yV2S`?CT5_>VU7ZSAjOjI=<CS9u(6-}Pa-RjuaN8!
z<jcTNrN+R}(89p*3n<j^f`OsbfPvvv0t1893<d`Af;qbaZGaLy0X`wFK>FjGH{Nb;
zK*kCWpQS*Gu_VYZn8D%MjWiG^$=lt9p@UV{1IXbl@Q5sCU=ULUVMfm&l@CBc_7YED
zSN2y-+(O#2cjOjy1NC%xx;TbZ+<JS?ke5M0fMtVmr)P-K<_$G=ESI!0Hc3x^;^R#P
z@VYyUYYSCCTI00A1HzVGb*Dn;c)vb-jcFpozT1*PW*Wd~QY~?fC`m~yNwrEYN(E93
zMg~S^x&}tNhK3=A7FH&PRz^nJ1_o9J26?+;7NKa!%}>cptHiBA{`nI*paup{S3j3^
HP6<r_YMlsn
literal 0
HcmV?d00001
diff --git a/packages/console/app/src/asset/brand/opencode-logo-dark-square.svg b/packages/console/app/src/asset/brand/opencode-logo-dark-square.svg
new file mode 100644
index 0000000..6a67f62
--- /dev/null
+++ b/packages/console/app/src/asset/brand/opencode-logo-dark-square.svg
@@ -0,0 +1,18 @@
+<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(30, 0)">
+<g clip-path="url(#clip0_1401_86283)">
+<mask id="mask0_1401_86283" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
+<path d="M240 0H0V300H240V0Z" fill="white"/>
+</mask>
+<g mask="url(#mask0_1401_86283)">
+<path d="M180 240H60V120H180V240Z" fill="#4B4646"/>
+<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#F1ECEC"/>
+</g>
+</g>
+</g>
+<defs>
+<clipPath id="clip0_1401_86283">
+<rect width="240" height="300" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/packages/console/app/src/asset/brand/opencode-logo-light-square.png b/packages/console/app/src/asset/brand/opencode-logo-light-square.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c710474abc4668504cb9678da1de6ad33458af9
GIT binary patch
literal 697
zcmeAS@N?(olHy`uVBq!ia0y~yV2S`?CT5_>VU7ZSAjOjI=<CS9u(6-}Pa-RjuaN8!
z<jcTNrN+R}(89p*3n<j^f`OsbfPvvv0t1893<d`Af;qbaZGaLy0X`wFKw42w?)<s4
zK*kyVm4AQ~V@Z%-FoVOh8)+a;lDE4HLkFv@2av;A;1OBOz#ygy!i=6lDj$G?>?NMQ
zuI#UvxP`Q3@5n9a2I}eXba4!+xb^m&Auof10LupBPR|gd%^Pa$ST1R0Y?7Y-#K)To
z;B|Kx*A}XPw8m+J2ZSxX>Q05w@qT^w8q-9EeYYip%rt<}q*~${QIe8al4_M)lnSI6
zj0}v-bPbGj4GlvKEv!rot&EJc4GgRd4DxoxEJD$co1c=IR*74K{PQPrKn)C@u6{1-
HoD!M<*)j+`
literal 0
HcmV?d00001
diff --git a/packages/console/app/src/asset/brand/opencode-logo-light-square.svg b/packages/console/app/src/asset/brand/opencode-logo-light-square.svg
new file mode 100644
index 0000000..a738ad8
--- /dev/null
+++ b/packages/console/app/src/asset/brand/opencode-logo-light-square.svg
@@ -0,0 +1,18 @@
+<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(30, 0)">
+<g clip-path="url(#clip0_1401_86274)">
+<mask id="mask0_1401_86274" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
+<path d="M240 0H0V300H240V0Z" fill="white"/>
+</mask>
+<g mask="url(#mask0_1401_86274)">
+<path d="M180 240H60V120H180V240Z" fill="#CFCECD"/>
+<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#211E1E"/>
+</g>
+</g>
+</g>
+<defs>
+<clipPath id="clip0_1401_86274">
+<rect width="240" height="300" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/packages/console/app/src/asset/brand/preview-opencode-logo-dark-square.png b/packages/console/app/src/asset/brand/preview-opencode-logo-dark-square.png
new file mode 100644
index 0000000000000000000000000000000000000000..604ad7aa7a87c71d4a3972f18da4044e53f745fe
GIT binary patch
literal 1477
zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9LzwG?TN?!0V$SrM_)$<hK>E)e-c@Ne1&9>
zAYTTCDm4a%h86~fUqGRT7Yq!g1`G_Z5*Qe)W-u^_7tGleXakf83GfMV1=1hiyun28
zU%gs1cdnz2jklZIqq}#b!@|5=U4d+%%7=IEM1_VzxRD_to-Qsx1sTc7KrJ6$zXlo@
zFuxY$Hi42LzhDLii5Gug>4^i8#NSshUI=lk@B-x+lf2zs7&=&GJ%Aj}0*}aI1_m)z
z5N7lYQuzQBWH0gbb!C6W#4V()m9m%>=ouDUPZ!6Kid%0la{4g^2(UVS^9^9Ux#z#*
zUM9{NA)j{pe@M$Rs$BIl=U=S8$$a1WzrRdo{gl+0z}h6r5vC9^6c`^Bx}VO;+bO}5
zvNP)ZgKMjwCMdj@`|<2Y_0Pt}1ZL)gY-~-uJS@@@9A*XrISC3k4mfNWo)RazbGPm0
z-*LChSmMOL4?kJISKi=fE3%ne|KQy6WQCxi5SK$J*`YY~T7$y$*OKq5Bzd2d?Vs~N
z@B8fph5p+U-+hzde)4_q^MCH|=dNhx2_G663hFce=0A)TEv>6k&v^?1%NErV*NBpo
z#FA92<f2p{#b9J$WTtCiq-$sxVrXGyVr*q(scm3jWnhruaU&H)LvDUbW?Cg~4U(b>
RH-Q=$JYD@<);T3K0RY|)#{mEU
literal 0
HcmV?d00001
diff --git a/packages/console/app/src/asset/brand/preview-opencode-logo-light-square.png b/packages/console/app/src/asset/brand/preview-opencode-logo-light-square.png
new file mode 100644
index 0000000000000000000000000000000000000000..3964d8528440323730053e56f9d957539937b99d
GIT binary patch
literal 1467
zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9LzwG?TN?!0V$SrM_)$<hK>E)e-c@Ne1&9>
zAYTTCDm4a%h86~fUqGRT7Yq!g1`G_Z5*Qe)W-u^_7tGleXakh+3-AeX1=5Oga+pX-
zUcR`n@W%CP=g*zh*VWm(dpD2)RHh&+YpAEUZ|@!;cmKY<7tWv4*U?$OZY@wf(5P=q
zw@e07{3Stt!3+!%FaEyL69*!Rzpq|wUamVGD8-oM?e4<R!7A$k<Zu>vL>4nJh^c}w
zqi2xH2cRH(iKnkC`zt1HA#JUc#jHSIuvmGzIEGZ*dV8@wmnlGi)v?HEMncg4sk@lD
zIaT_<lxtU%8_(QxrNqqsn)KQ9%h&DxCN=JfRETIyU~LlR2pb9vn-fWv%cJ$!m?PI+
zGv4tv%TnURa`rlle{ppQ3O5coY-nsuU}iqZ#@58k!y+xgVP+tZGdv|e#VtQu_MLV6
z?L5Ea#y9-;OWOY?C_F#SfA^^jN9a(9$sv^JP@HNicjD`}`}Y4=-!3(o@cVI9<8Hfm
z&5bti(|7$Y)|v3Q^Zn-UpA;4kk?aKV*|pOO`<V}&_gU>d_YAOLQ7v(eC`m~yNwrEY
zN(E93Mg~S^x&}tNhK3=A7FH(4Rz{ZE1_o9J1{oeVQc*PI=BH$)RpQnlDVlH-sDZ)L
L)z4*}Q$iB}B`L3|
literal 0
HcmV?d00001
diff --git a/packages/console/app/src/routes/brand/index.tsx b/packages/console/app/src/routes/brand/index.tsx
index eda3c84..9140462 100644
--- a/packages/console/app/src/routes/brand/index.tsx
+++ b/packages/console/app/src/routes/brand/index.tsx
@@ -7,18 +7,24 @@ import { useI18n } from "~/context/i18n"
import { LocaleLinks } from "~/component/locale-links"
import previewLogoLight from "../../asset/brand/preview-opencode-logo-light.png"
import previewLogoDark from "../../asset/brand/preview-opencode-logo-dark.png"
+import previewLogoLightSquare from "../../asset/brand/preview-opencode-logo-light-square.png"
+import previewLogoDarkSquare from "../../asset/brand/preview-opencode-logo-dark-square.png"
import previewWordmarkLight from "../../asset/brand/preview-opencode-wordmark-light.png"
import previewWordmarkDark from "../../asset/brand/preview-opencode-wordmark-dark.png"
import previewWordmarkSimpleLight from "../../asset/brand/preview-opencode-wordmark-simple-light.png"
import previewWordmarkSimpleDark from "../../asset/brand/preview-opencode-wordmark-simple-dark.png"
import logoLightPng from "../../asset/brand/opencode-logo-light.png"
import logoDarkPng from "../../asset/brand/opencode-logo-dark.png"
+import logoLightSquarePng from "../../asset/brand/opencode-logo-light-square.png"
+import logoDarkSquarePng from "../../asset/brand/opencode-logo-dark-square.png"
import wordmarkLightPng from "../../asset/brand/opencode-wordmark-light.png"
import wordmarkDarkPng from "../../asset/brand/opencode-wordmark-dark.png"
import wordmarkSimpleLightPng from "../../asset/brand/opencode-wordmark-simple-light.png"
import wordmarkSimpleDarkPng from "../../asset/brand/opencode-wordmark-simple-dark.png"
import logoLightSvg from "../../asset/brand/opencode-logo-light.svg"
import logoDarkSvg from "../../asset/brand/opencode-logo-dark.svg"
+import logoLightSquareSvg from "../../asset/brand/opencode-logo-light-square.svg"
+import logoDarkSquareSvg from "../../asset/brand/opencode-logo-dark-square.svg"
import wordmarkLightSvg from "../../asset/brand/opencode-wordmark-light.svg"
import wordmarkDarkSvg from "../../asset/brand/opencode-wordmark-dark.svg"
import wordmarkSimpleLightSvg from "../../asset/brand/opencode-wordmark-simple-light.svg"
@@ -135,6 +141,60 @@ export default function Brand() {
</button>
</div>
</div>
+ <div>
+ <img src={previewLogoLightSquare} alt="OpenCode brand guidelines" />
+ <div data-component="actions">
+ <button onClick={() => downloadFile(logoLightSquarePng, "opencode-logo-light-square.png")}>
+ PNG
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path
+ d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
+ stroke="currentColor"
+ stroke-width="1.5"
+ stroke-linecap="square"
+ />
+ </svg>
+ </button>
+ <button onClick={() => downloadFile(logoLightSquareSvg, "opencode-logo-light-square.svg")}>
+ SVG
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path
+ d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
+ stroke="currentColor"
+ stroke-width="1.5"
+ stroke-linecap="square"
+ />
+ </svg>
+ </button>
+ </div>
+ </div>
+ <div>
+ <img src={previewLogoDarkSquare} alt="OpenCode brand guidelines" />
+ <div data-component="actions">
+ <button onClick={() => downloadFile(logoDarkSquarePng, "opencode-logo-dark-square.png")}>
+ PNG
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path
+ d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
+ stroke="currentColor"
+ stroke-width="1.5"
+ stroke-linecap="square"
+ />
+ </svg>
+ </button>
+ <button onClick={() => downloadFile(logoDarkSquareSvg, "opencode-logo-dark-square.svg")}>
+ SVG
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path
+ d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
+ stroke="currentColor"
+ stroke-width="1.5"
+ stroke-linecap="square"
+ />
+ </svg>
+ </button>
+ </div>
+ </div>
<div>
<img src={previewWordmarkLight} alt="OpenCode brand guidelines" />
<div data-component="actions">
--
2.39.5