From 8e4dbf6e0f27c020fab29d2072df6e7823fcbcad Mon Sep 17 00:00:00 2001 From: Rodrigo Tornis Date: Wed, 4 Feb 2026 14:58:53 -0300 Subject: [PATCH 1/2] feat: add share server with authentication and cleanup functionality - Created package.json for share-server with necessary dependencies and scripts. - Implemented authentication middleware with session management in auth.ts. - Added session cleanup functionality to remove expired shares in cleanup.ts. - Set up server initialization and logging in index.ts. - Developed logging utility for tracking server activities in logger.ts. - Built the main server using Hono framework in server.ts with API and web UI routes. - Created share management logic in share.ts for creating, retrieving, syncing, and deleting shares. - Implemented file-based storage system in storage-fs.ts for persistent data storage. - Designed HTML templates for login and share views in templates.ts and login.html. - Configured TypeScript settings in tsconfig.json for the share-server package. --- .opencode/agent/duplicate-pr.md | 26 -- .opencode/agent/k8s-diagnostic.md | 65 +++ .opencode/opencode.jsonc | 19 +- .opencode/rules/RULES-K8S.md | 72 ++++ CLAUDE.md | 371 ++++++++++++++++++ GEMINI.md | 51 +++ bun.lock | 16 + deploy-docs.ts | 0 packages/share-server/.env.example | 16 + packages/share-server/.gitignore | 6 + packages/share-server/INSTALL.md | 295 ++++++++++++++ packages/share-server/README.md | 334 ++++++++++++++++ packages/share-server/package.json | 20 + packages/share-server/src/auth.ts | 111 ++++++ packages/share-server/src/cleanup.ts | 46 +++ packages/share-server/src/index.ts | 46 +++ packages/share-server/src/logger.ts | 37 ++ packages/share-server/src/server.ts | 134 +++++++ packages/share-server/src/share.ts | 165 ++++++++ packages/share-server/src/storage-fs.ts | 139 +++++++ packages/share-server/src/templates.ts | 324 +++++++++++++++ .../share-server/src/templates/login.html | 101 +++++ packages/share-server/tsconfig.json | 19 + 23 files changed, 2379 insertions(+), 34 deletions(-) delete mode 100644 .opencode/agent/duplicate-pr.md create mode 100644 .opencode/agent/k8s-diagnostic.md create mode 100644 .opencode/rules/RULES-K8S.md create mode 100644 CLAUDE.md create mode 100644 GEMINI.md create mode 100644 deploy-docs.ts create mode 100644 packages/share-server/.env.example create mode 100644 packages/share-server/.gitignore create mode 100644 packages/share-server/INSTALL.md create mode 100644 packages/share-server/README.md create mode 100644 packages/share-server/package.json create mode 100644 packages/share-server/src/auth.ts create mode 100644 packages/share-server/src/cleanup.ts create mode 100644 packages/share-server/src/index.ts create mode 100644 packages/share-server/src/logger.ts create mode 100644 packages/share-server/src/server.ts create mode 100644 packages/share-server/src/share.ts create mode 100644 packages/share-server/src/storage-fs.ts create mode 100644 packages/share-server/src/templates.ts create mode 100644 packages/share-server/src/templates/login.html create mode 100644 packages/share-server/tsconfig.json diff --git a/.opencode/agent/duplicate-pr.md b/.opencode/agent/duplicate-pr.md deleted file mode 100644 index c9c932ef790d..000000000000 --- a/.opencode/agent/duplicate-pr.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -mode: primary -hidden: true -model: opencode/claude-haiku-4-5 -color: "#E67E22" -tools: - "*": false - "github-pr-search": true ---- - -You are a duplicate PR detection agent. When a PR is opened, your job is to search for potentially duplicate or related open PRs. - -Use the github-pr-search tool to search for PRs that might be addressing the same issue or feature. - -IMPORTANT: The input will contain a line `CURRENT_PR_NUMBER: NNNN`. This is the current PR number, you should not mark that the current PR as a duplicate of itself. - -Search using keywords from the PR title and description. Try multiple searches with different relevant terms. - -If you find potential duplicates: - -- List them with their titles and URLs -- Briefly explain why they might be related - -If no duplicates are found, say so clearly. BUT ONLY SAY "No duplicate PRs found" (don't say anything else if no dups) - -Keep your response concise and actionable. diff --git a/.opencode/agent/k8s-diagnostic.md b/.opencode/agent/k8s-diagnostic.md new file mode 100644 index 000000000000..a6a2b470fc3c --- /dev/null +++ b/.opencode/agent/k8s-diagnostic.md @@ -0,0 +1,65 @@ +--- +description: Agente SRE especializado em diagnóstico de clusters Kubernetes, análise de logs e rastreio de anomalias. +mode: primary +color: "#12C0FF" +tools: + write: false + edit: false + bash: true +--- + +# Perfil e Objetivo +Você é o **k8s-diagnostic**, um Engenheiro de Confiabilidade de Site (SRE) especializado em ambientes críticos. Sua missão é realizar diagnósticos precisos em clusters Kubernetes usando o **MCP Server dedicado**, focando em identificar a causa raiz de falhas e sugerir correções rápidas. + +# Protocolo de Operação (Fluxo Obrigatório) + +### 1. Identificação de Contexto e Escopo +Antes de iniciar qualquer análise técnica, você deve: +1. **Contexto:** Listar os contextos disponíveis via MCP e perguntar ao usuário: *"Identifiquei os contextos [Contexto A] e [Contexto B]. Qual deles devemos utilizar para a análise?"*. +2. **Namespace e Alvo:** Solicitar o namespace (com foco em `dados-production` ou `egam-production`) e o prefixo dos pods que devem ser analisados. + +### 2. Diagnóstico Técnico via MCP Server +Ao receber o alvo, execute as seguintes etapas silenciosamente antes de gerar o relatório: +- **Saúde dos Pods:** Liste os pods no namespace e identifique estados como `CrashLoopBackOff`, `Pending` ou `OOMKilled`. +- **Análise de Eventos:** Verifique `kubectl get events` em busca de warnings de rede, storage ou falhas de agendamento. +- **Rastreio de Deploys:** Verifique a data de criação dos ReplicaSets/Deployments para determinar se houve um redeploy recente (nas últimas 24h). +- **Varredura de Logs:** Analise os logs dos pods afetados filtrando por `ERROR`, `WARNING`, `Exception` e códigos `5xx`. Verifique sempre o log anterior (`previous: true`) se o pod tiver reiniciado. + +# Diretrizes de Resposta +- **Idioma:** Responda sempre em **Português**. +- **Tom:** Profissional, direto e focado em dados técnicos. +- **Foco:** Priorize problemas nos namespaces `dados-production` e `egam-production`. + +# Formato de Saída (Relatório de Diagnóstico) + +Sua resposta final deve seguir exatamente esta estrutura: + +--- +### 📑 Relatório de Diagnóstico SRE - [NOME_DO_POD/SERVIÇO] + +**1. Resumo do Status** +> **[STATUS]:** (🔴 CRÍTICO | 🟡 ALERTA | 🟢 ESTÁVEL) +> *Breve descrição em linguagem simples do que está acontecendo agora.* + +**2. Diagnóstico Técnico (Causa Raiz)** +- **Anomalia Detectada:** Detalhes sobre o erro (ex: Falha de conexão, Erro de sintaxe, Falta de memória). +- **Evidência (Logs/Eventos):** ```text + [Cole aqui o trecho relevante extraído via MCP filtrando Erros/Warnings] + +* **Saúde da Infraestrutura:** Status dos recursos (CPU/Memória) e dependências (Service Mesh/DB). + +**3. Histórico de Mudanças** + +* **Deploys Recentes:** (Informa se houve alteração na imagem ou configuração recentemente). + +**4. Ações de Correção e Melhoria** + +* **Imediato:** [Ação para restaurar o serviço] +* **Melhoria:** [Ação para evitar que o erro se repita] + +--- + +# Restrições + +* Não tome ações de escrita (delete/patch) sem confirmação explícita do usuário. +* Sempre valide se os componentes de infraestrutura (`istio`, `calico`, `cert-manager`) estão saudáveis se os pods de aplicação apresentarem erros de rede ou segurança. \ No newline at end of file diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index 5d2dec625c68..e8a95d9def20 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -4,20 +4,23 @@ // "enterprise": { // "url": "https://enterprise.dev.opencode.ai", // }, - "instructions": ["STYLE_GUIDE.md"], + "enterprise": { + "url": "http://localhost:3000", + }, + "instructions": ["STYLE_GUIDE.md",".opencode/rules/*.md"], + "plugin": ["opencode-scheduler"], "provider": { "opencode": { "options": {}, }, }, "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp", + "kubernetes": { + "enabled": true, + "type": "local", + "command": ["npx", "-y", "mcp-server-kubernetes"], + "environment": {}, }, }, - "tools": { - "github-triage": false, - "github-pr-search": false, - }, + "tools": {}, } diff --git a/.opencode/rules/RULES-K8S.md b/.opencode/rules/RULES-K8S.md new file mode 100644 index 000000000000..79db27b0a8ea --- /dev/null +++ b/.opencode/rules/RULES-K8S.md @@ -0,0 +1,72 @@ +# Regras de Operação Kubernetes via MCP Server (Ambientes Produção) + +## 1. Escopo de Atuação e Priorização + +* **Contextos Alvo:** `egam-production-main` e `gam-production-main`. +* **Namespaces Prioritários:** `dados-production` e `egam-production`. +* **Ferramenta de Execução:** Todas as consultas devem ser realizadas exclusivamente através do **MCP Server dedicado para Kubernetes**. +* **Idioma de Resposta:** Todas as interações, diagnósticos e relatórios devem ser em **Português**. + +## 2. Protocolo de Diagnóstico de Saúde (Saúde, Erros e Logs) + +Ao ser acionado para qualquer análise, o LLM deve seguir este fluxo lógico via MCP: + +1. **Verificação de Status:** Listar pods nos namespaces alvo e filtrar qualquer status diferente de `Running` (ex: `CrashLoopBackOff`, `Pending`, `Error`, `Evicted`). +2. **Análise de Eventos:** Consultar `kubectl get events` no namespace para identificar warnings recentes de agendamento, falhas de liveness/readiness ou pressão de recursos. +3. **Filtragem de Logs (Crucial):** Ao encontrar pods instáveis, executar a leitura de logs filtrando especificamente por termos como `ERROR`, `WARNING`, `FATAL`, `EXCEPTION` ou `5XX`. +* *Nota:* Sempre verificar o log anterior (`previous: true`) em caso de restarts recentes. + + + +## 3. Rastreabilidade de Mudanças (Deploys e Redeploys) + +Para identificar se uma falha foi causada por uma alteração recente, o LLM deve: + +1. **Verificar Timeline:** Listar os `ReplicaSets` e `Deployments` ordenados por data de criação/modificação. +2. **Rollout Status:** Verificar se houve um rollout recente e se ele foi concluído com sucesso. +3. **Comparação de Imagem:** Identificar se a tag da imagem do container foi alterada nas últimas 24 horas. + +## 4. Monitoramento de Infraestrutura Crítica + +Mesmo que o foco sejam os namespaces de aplicação, o LLM deve reportar se falhas no `dados-production` estão relacionadas a componentes de base: + +* **Storage/DB:** Validar saúde dos pods de `pgpool`, `redis` ou `mysql` e checar eventos de `PersistentVolumeClaims` (PVC). +* **Rede:** Em caso de erros de conexão, verificar a saúde do `calico-node` ou `istio-proxy` (sidecar) nos respectivos pods. + +--- + +## 5. Formato de Resposta Técnica (Report Padrão) + +O LLM deve responder seguindo rigorosamente esta estrutura para garantir clareza e agilidade na tomada de decisão: + +### 📑 Relatório de Operação Kubernetes + +**1. Resumo do Status (Saúde Geral)** + +> [STATUS]: (🔴 CRÍTICO | 🟡 ALERTA | 🟢 ESTÁVEL) +> *Breve descrição do estado atual dos namespaces `egam-production` e `dados-production`.* + +**2. Diagnóstico de Erros e Logs** + +* **Pod Afetado:** `[Nome do Pod]` +* **Causa Identificada:** (Ex: Falha na conexão com DB / Erro de Memória) +* **Filtro de Log Relevante:** +> `[Inserir aqui o trecho do log contendo o erro/warning identificado via MCP]` + + +* **Saúde dos Componentes:** (Status de CPU/Memória e Readiness Probe) + +**3. Histórico de Alterações (Deploys)** + +* **Recentemente modificado?** (Sim/Não - listar última alteração se houver). +* **Impacto do Redeploy:** (Se o erro começou após a nova versão). + +**4. Plano de Ação (Sugestão)** + +* *Exemplo: "Executar o rollout undo do deployment X" ou "Aumentar o limite de memória para o container Y".* + +--- + +### Exemplo de comando que você pode dar ao OpenCode após configurar estas regras: + +> *"Verifique a saúde do namespace dados-production agora. Identifique se houve redeploys na última hora e, se houver erros nos logs, filtre apenas os Warnings e Errors para mim."* \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000000..e6a1e32b1d95 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,371 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Language Preference + +**IMPORTANT: Always respond in Portuguese (pt-BR) when interacting with users in this repository.** + +## Common Commands + +### Development +```bash +# Install dependencies +bun install + +# Run OpenCode in dev mode (defaults to packages/opencode directory) +bun dev + +# Run against a different directory +bun dev + +# Run against the repo root +bun dev . + +# Run with inspector for debugging +bun run --inspect=ws://localhost:6499/ dev +``` + +### Building +```bash +# Build standalone executable +./packages/opencode/script/build.ts --single + +# Run built executable +./packages/opencode/dist/opencode-/bin/opencode + +# Build desktop app +bun run --cwd packages/desktop tauri build +``` + +### Testing +```bash +# Run tests in opencode package +bun --cwd packages/opencode test + +# Run specific test file +bun test + +# Run app e2e tests +bun --cwd packages/app test +bun --cwd packages/app test:e2e:ui # with UI +``` + +### Code Generation +```bash +# Regenerate SDK and OpenAPI spec (required after API/server changes) +./script/generate.ts + +# Format code +./script/format.ts + +# Type checking +bun typecheck +``` + +### UI Development +```bash +# Run web app dev server (http://localhost:5173) +bun run --cwd packages/app dev + +# Run desktop app with dev server (http://localhost:1420) +bun run --cwd packages/desktop tauri dev + +# Desktop dev server only (no native window) +bun run --cwd packages/desktop dev +``` + +## Architecture Overview + +OpenCode is a monorepo with a client/server architecture. The core is a Hono-based server with multiple frontends (CLI, TUI, Web, Desktop). + +### Key Packages + +**Core:** +- `packages/opencode` - Main CLI, server, agent system, LSP, and core business logic +- `packages/sdk/js` - JavaScript SDK for interacting with OpenCode server +- `packages/plugin` - Plugin system interfaces and types +- `packages/util` - Shared utilities and error handling + +**Frontends:** +- `packages/opencode/src/cli/cmd/tui/` - Terminal UI (SolidJS + OpenTUI) +- `packages/app` - Web UI (SolidJS, shared with desktop) +- `packages/desktop` - Native desktop app (Tauri wrapper) + +**Other:** +- `packages/console` - Admin/enterprise console +- `packages/web` - Marketing/documentation website +- `packages/script` - Build scripts +- `packages/slack` - Slack integration + +### Server Architecture + +**Entry Point:** `packages/opencode/src/index.ts` +- Uses Yargs for CLI parsing +- Registers commands: `run`, `tui`, `serve`, `agent`, `auth`, `mcp`, `debug`, etc. + +**Server:** `packages/opencode/src/server/server.ts` +- Hono HTTP server on port 4096 +- Routes organized by feature: session, project, file, config, provider, mcp, permission, etc. +- OpenAPI documentation auto-generated +- CORS handling for localhost, Tauri, and whitelisted domains + +**Instance System:** `packages/opencode/src/project/instance.ts` +- Context-based per-directory state management +- Lazy initialization with caching +- Graceful disposal with cleanup + +### Agent System + +Three built-in agents (defined in `packages/opencode/src/agent/agent.ts`): + +- **`build`** - Default agent with full file access and bash execution +- **`plan`** - Read-only agent that denies edits and asks for bash permission +- **`general`** - Subagent for complex searches (invoked via `@general`) + +**Agent Types:** +- `primary` - Can be used as main agent +- `subagent` - Can be invoked by other agents +- `all` - Both primary and subagent + +**Custom Agents:** +- Frontmatter-based `.md` files +- Located in `~/.opencode/agent/` (global) or `.opencode/agent/` (project) +- Config: system prompt, tools, permissions, model override, temperature, etc. + +### Session Flow + +1. User sends message/command +2. Server builds message with context, history, tools +3. LLM processes via selected agent and model +4. Tool calls executed through permission system +5. Results streamed as SSE events +6. Parts (text, tool calls) stored in message + +**Session Data:** `packages/opencode/src/session/index.ts` +- ID, slug, project, directory, parent (for forks) +- Summary (diffs, file changes, additions/deletions) +- Compaction metadata (message summarization) +- Permissions and share URLs + +### Provider System + +**Bundled Providers** (via Vercel AI SDK): +Anthropic, OpenAI, Google, Azure, Bedrock, Mistral, Groq, DeepInfra, Cerebras, Cohere, TogetherAI, Perplexity, XAI, GitLab, OpenRouter, GitHub Copilot + +**Provider Flow:** +1. Load from config/environment +2. Create model instance via AI SDK +3. Apply ProviderTransform customizations +4. Stream/generate responses + +### Tool System + +**Core Tools** (`packages/opencode/src/tool/`): +- `Bash` - Command execution with output capture +- `Read`/`Write`/`Edit` - File operations with diff tracking +- `Glob` - File pattern matching +- `Grep` - Code searching +- External directory access control + +**Tool Execution:** +1. Permission check (agent + user config + session overrides) +2. Execute tool +3. Track results (diffs, output) +4. Stream events to client + +### Permission System + +**Permission Types:** +- Tool-level (bash, read, write, edit) +- File pattern matching (glob patterns) +- External directory access +- Plan mode entry/exit +- Doom loop detection + +**Actions:** `allow`, `deny`, `ask` + +**Hierarchy:** Agent permissions → User config → Session overrides + +### LSP Integration + +**LSP System** (`packages/opencode/src/lsp/`): +- Multiple language servers: Pyright, TypeScript, Rust Analyzer, etc. +- Dynamic client spawning on demand +- Caches symbols, document outlines, code intelligence +- Experimental Ty language server support (feature flag) + +### MCP (Model Context Protocol) + +**MCP System** (`packages/opencode/src/mcp/`): +- Server types: `remote` (HTTP/HTTPS with OAuth) and `local` (subprocess stdio) +- Tool listing/execution, prompts, resources +- OAuth authentication with browser redirect +- Dynamic client registration +- Timeout configuration + +**Transports:** +1. StreamableHTTP (primary) +2. SSE (fallback) +3. Stdio (local servers) + +### Plugin System + +**Plugin Interface** (`packages/plugin/src/index.ts`): + +**Hooks:** +- `event` - Listen to system events +- `config` - React to config changes +- `tool` - Define custom tools +- `auth` - Custom auth (OAuth or API key) +- `chat.message` / `chat.params` - Intercept messages/params +- `permission.ask` - Control permission decisions +- `command.execute.before/after` - Wrap command execution +- `tool.execute.before/after` - Wrap tool execution +- Experimental: message/system transforms, compaction, text completion + +**Auth Support:** +- OAuth flow with browser +- API key configuration +- Custom prompts (text/select) +- Conditional fields + +### Event Bus + +**Bus System** (`packages/opencode/src/bus/`): +- Global event bus for inter-component communication +- Server-sent events (SSE) for client subscriptions +- Event types: session events, permission requests, MCP changes + +### TUI Architecture + +**TUI App** (`packages/opencode/src/cli/cmd/tui/app.tsx`): +- SolidJS + `@opentui/solid` for terminal rendering +- Context providers: SDK, Sync, Local, Keybind, Theme, Route, Dialog +- Routes: home, session pages +- Interactive dialogs for permissions + +**Data Flow:** +1. TUI connects to server (internal fetch or HTTP attach) +2. SDK subscribes to SSE events +3. Streams session events (tool execution, text, errors) +4. Handles permission requests with dialogs +5. Supports session sharing/continuation + +## Style Guide (STYLE_GUIDE.md) + +- Keep logic within one function unless composable/reusable +- Avoid unnecessary destructuring +- Avoid `else` statements (prefer early returns or ternaries) +- Prefer `.catch()` over `try`/`catch` when possible +- Avoid `any` type +- Prefer `const` over `let` +- Use single-word variable names when descriptive +- Use Bun APIs like `Bun.file()` when appropriate + +## Contributing Notes + +### PR Requirements +- All PRs must reference an existing issue (use `Fixes #123` or `Closes #123`) +- Follow conventional commit format: `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:` +- Optional scope: `feat(app):`, `fix(desktop):`, `chore(opencode):` +- Keep PRs small and focused +- Include screenshots/videos for UI changes +- Explain how you verified logic changes work + +### Changes Likely to Be Accepted +- Bug fixes +- Additional LSPs/formatters +- Improvements to LLM performance +- Support for new providers +- Fixes for environment-specific quirks +- Missing standard behavior +- Documentation improvements + +### Design Review Required +UI or core product features must go through design review with core team before implementation. + +### After API/Server Changes +Run `./script/generate.ts` to regenerate SDK and OpenAPI spec. + +## Package Manager + +This project uses **Bun 1.3+** as the package manager. All commands should use `bun` instead of `npm`/`yarn`/`pnpm`. + +## Debugging + +### Bun Debugging +```bash +# Manual debug (most reliable) +bun run --inspect=ws://localhost:6499/ dev + +# For TUI + server breakpoints +bun dev spawn + +# Debug server separately +bun run --inspect=ws://localhost:6499/ ./src/index.ts serve --port 4096 +# Then attach: opencode attach http://localhost:4096 + +# Debug TUI separately +bun run --inspect=ws://localhost:6499/ --conditions=browser ./src/index.ts +``` + +**Tip:** Set `export BUN_OPTIONS=--inspect=ws://localhost:6499/` to avoid repeating flag. + +### VSCode Setup +See `.vscode/settings.example.json` and `.vscode/launch.example.json` for example configurations. + +## Data Flow (High Level) + +``` +CLI Input + ↓ +Yargs Parser + ↓ +Command Handler (e.g., RunCommand) + ↓ +Server.App (Hono) + ↓ +Session Routes + ↓ +Session Manager + ↓ +LLM Provider Selection + ↓ +Agent & Permission Check + ↓ +Tool Execution Loop + ├→ Bash Tool + ├→ File Tools (Read/Edit/Write) + ├→ Grep/Glob (Code Search) + ├→ LSP (Code Intelligence) + └→ MCP Tools (External Tools) + ↓ +Message Parts Storage + ↓ +Event Streaming (SSE) + ↓ +TUI/App Display +``` + +## Important Patterns + +### Context-Based Dependency Injection +- Instance provides context for all operations +- Directory-scoped state management +- Lazy initialization with cleanup on disposal + +### Event-Driven Architecture +- Bus for internal communication +- SSE for client updates +- Permission requests as events + +### Provider Pattern +- Pluggable LLM providers via AI SDK +- Dynamic provider loading from config +- Custom loaders for special cases + +### Hook System +- Plugins extend functionality via hooks +- Event-based integration points +- Pre/post execution handlers diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 000000000000..246621ce7981 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,51 @@ +# Memória de Projeto: OpenCode + +Este documento descreve a arquitetura, funcionalidades e estrutura do projeto **OpenCode**, com base na análise da configuração do monorepositório e seus pacotes. + +## 1. O que é a Solução? + +A solução é uma plataforma de desenvolvimento assistida por IA chamada **OpenCode**, conforme descrito no `package.json` raiz ("AI-powered development tool"). + +O projeto é estruturado como um **monorepositório**, gerenciado com `Bun Workspaces` e `Turborepo`. Ele é composto por múltiplos pacotes que, juntos, formam a plataforma OpenCode. O objetivo é fornecer um conjunto de ferramentas integradas que auxiliam desenvolvedores em suas tarefas diárias. + +## 2. Funcionalidades + +Com base na estrutura de pacotes e dependências, as principais funcionalidades do ecossistema OpenCode incluem: + +- **Aplicação Principal (`packages/opencode`):** O ponto de entrada do ecossistema, provavelmente uma ferramenta de linha de comando (CLI) ou uma aplicação desktop que orquestra as outras funcionalidades. +- **SDK (`packages/sdk`):** Um kit de desenvolvimento de software que permite a interação programática com os serviços e funcionalidades do OpenCode. +- **Servidor de Compartilhamento (`packages/share-server`):** Um microsserviço dedicado a compartilhar sessões de desenvolvimento, permitindo colaboração ou revisão. +- **Integrações Externas (`packages/slack`):** Módulos para integrar o OpenCode com outras plataformas, como o Slack, notificando ou recebendo comandos. +- **Componentes de UI/Console (`packages/console`):** Pacotes que provavelmente contêm interfaces web ou componentes de UI, possivelmente construídos com **Solid.js** (conforme indicado nas dependências). +- **Plugins e Scripts (`@opencode-ai/plugin`, `@opencode-ai/script`):** Uma arquitetura de plugins que permite estender as funcionalidades da ferramenta principal. + +## 3. Localização dos Artefatos + +- **Código-Fonte dos Pacotes:** Todo o código-fonte está organizado dentro do diretório `/packages/`. Cada subdiretório representa um pacote independente (ex: `packages/sdk`, `packages/share-server`). +- **Configuração do Monorepo:** O arquivo principal de configuração é o `package.json` na raiz do projeto, que define os `workspaces`, scripts globais e dependências centralizadas. +- **Gerenciamento de Build:** A ferramenta `Turborepo` (indicada como dependência de desenvolvimento) gerencia o cache e a execução de tarefas (como `build`, `test`, `typecheck`) de forma otimizada em todo o monorepo. +- **Gerenciamento de Dependências:** As versões das dependências são centralizadas no `package.json` raiz usando a propriedade `workspaces.catalog`. Isso garante consistência entre todos os pacotes. + +## 4. Estrutura de Código + +O projeto adota uma abordagem moderna e escalável para o desenvolvimento de software: + +- **Monorepo com `Bun Workspaces` + `Turborepo`:** Essa combinação permite o desenvolvimento de múltiplos pacotes em um único repositório, facilitando o compartilhamento de código (ex: `@opencode-ai/util`), a consistência de dependências e a execução otimizada de tarefas em todo o projeto. +- **Gerenciamento Centralizado de Versões (`catalog`):** O uso de `workspace:*` e `"dependency": "catalog:"` nos `package.json` dos pacotes aponta para o catálogo de versões no `package.json` raiz. Isso evita conflitos e simplifica a atualização de bibliotecas. +- **Qualidade de Código:** O uso de `Husky` para ganchos de Git e `Prettier` para formatação de código garante que o código enviado ao repositório siga um padrão de qualidade e estilo. + +## 5. Arquitetura de Software + +A arquitetura geral do OpenCode é baseada em **composição de pacotes e microsserviços** dentro de um monorepo. + +- **Padrão Arquitetural:** A solução é modular, onde uma aplicação central consome funcionalidades de outros pacotes locais (SDK, utilitários) e se comunica com serviços independentes (como o `share-server`). +- **Stack Tecnológico:** + - **Runtime e Tooling:** **Bun** é usado como runtime, gerenciador de pacotes e executor de scripts, escolhido por sua alta performance. + - **Linguagem:** **TypeScript** é usado em todo o projeto, garantindo segurança de tipos e melhor manutenibilidade. + - **Otimização de Monorepo:** **Turborepo** para builds e tarefas em cache. + - **Backend:** Microsserviços construídos com **Hono**, um framework web minimalista e rápido. + - **Frontend (inferido):** Interfaces web construídas com **Solid.js**, um framework reativo e performático. + - **Deployment (inferido):** A presença da dependência `sst` (Serverless Stack) sugere que partes da infraestrutura podem ser implantadas em plataformas serverless como AWS. + +Essa arquitetura de monorepo com pacotes bem definidos é ideal para um projeto complexo como o OpenCode, pois promove a reutilização de código, a separação de responsabilidades e permite que equipes diferentes trabalhem em partes distintas do sistema de forma independente e coesa. + diff --git a/bun.lock b/bun.lock index f515b8cab7dc..61ebe37f46ec 100644 --- a/bun.lock +++ b/bun.lock @@ -390,6 +390,20 @@ "typescript": "catalog:", }, }, + "packages/share-server": { + "name": "@opencode-ai/share-server", + "version": "0.1.0", + "dependencies": { + "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/util": "workspace:*", + "hono": "catalog:", + "zod": "catalog:", + }, + "devDependencies": { + "@types/bun": "catalog:", + "typescript": "catalog:", + }, + }, "packages/slack": { "name": "@opencode-ai/slack", "version": "1.1.28", @@ -1211,6 +1225,8 @@ "@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk/js"], + "@opencode-ai/share-server": ["@opencode-ai/share-server@workspace:packages/share-server"], + "@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"], "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"], diff --git a/deploy-docs.ts b/deploy-docs.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/share-server/.env.example b/packages/share-server/.env.example new file mode 100644 index 000000000000..60a0aab82501 --- /dev/null +++ b/packages/share-server/.env.example @@ -0,0 +1,16 @@ +# Configuração do Servidor +PORT=3000 +HOST=0.0.0.0 + +# Diretórios de Armazenamento +SHARE_DATA_DIR=~/opencode-shares/data +SHARE_LOG_DIR=~/opencode-shares/logs + +# Autenticação +SHARE_AUTH_ENABLED=true +SHARE_AUTH_USERNAME=admin +SHARE_AUTH_PASSWORD=changeme + +# TTL (Time to Live) - Auto-delete de shares antigos +SHARE_TTL_ENABLED=true +SHARE_TTL_DAYS=7 diff --git a/packages/share-server/.gitignore b/packages/share-server/.gitignore new file mode 100644 index 000000000000..929df617398e --- /dev/null +++ b/packages/share-server/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +data/ +*.log +.env +.DS_Store diff --git a/packages/share-server/INSTALL.md b/packages/share-server/INSTALL.md new file mode 100644 index 000000000000..e70ecb187c3a --- /dev/null +++ b/packages/share-server/INSTALL.md @@ -0,0 +1,295 @@ +# 🚀 Guia de Instalação Rápida - OpenCode Share Server + +## ✅ Servidor Implementado com Sucesso! + +Todas as funcionalidades foram implementadas e testadas: + +- ✅ API REST completa (4 endpoints) +- ✅ Storage filesystem local (`~/opencode-shares/`) +- ✅ Autenticação com login/senha +- ✅ Interface web para visualizar shares +- ✅ Sistema de logging detalhado +- ✅ TTL com auto-delete de shares antigos +- ✅ Acesso via rede local (LAN) + +## 📦 Instalação + +### 1. Instalar Dependências + +```bash +cd packages/share-server +bun install +``` + +### 2. Configurar Variáveis (Opcional) + +```bash +# Copiar arquivo de exemplo +cp .env.example .env + +# Editar configurações (opcional) +nano .env +``` + +**Configurações padrão:** +- Porta: 3000 +- Host: 0.0.0.0 (rede local) +- Usuário: admin +- Senha: opencode +- TTL: 7 dias + +## 🎯 Uso + +### Iniciar Servidor + +```bash +# Modo desenvolvimento (com hot reload) +bun dev + +# Modo produção +bun start + +# Com porta customizada +PORT=8080 bun dev +``` + +### Configurar Cliente OpenCode + +Edite `~/.opencode/opencode.json`: + +```json +{ + "enterprise": { + "url": "http://localhost:3000" + } +} +``` + +Para acessar de outra máquina na rede: + +```json +{ + "enterprise": { + "url": "http://192.168.1.100:3000" + } +} +``` + +### Acessar Interface Web + +1. Abra o navegador em: http://localhost:3000 +2. Faça login: + - **Usuário:** admin + - **Senha:** opencode +3. Visualize todos os compartilhamentos criados + +## 🧪 Testes + +### 1. Health Check + +```bash +curl http://localhost:3000/health +``` + +Resposta esperada: +```json +{ + "status": "ok", + "timestamp": "2026-01-21T02:27:05.735Z" +} +``` + +### 2. Criar Compartilhamento + +```bash +curl -X POST http://localhost:3000/api/share \ + -H "Content-Type: application/json" \ + -d '{"sessionID":"test_12345678"}' +``` + +Resposta esperada: +```json +{ + "id": "test_12345678", + "secret": "uuid-aqui", + "url": "http://localhost:3000/share/test_12345678" +} +``` + +### 3. Obter Dados do Share + +```bash +curl http://localhost:3000/api/share/test_12345678/data +``` + +### 4. Verificar Storage Local + +```bash +# Ver arquivos criados +ls ~/opencode-shares/data/share/ + +# Ver conteúdo do share +cat ~/opencode-shares/data/share/test_12345678.json +``` + +### 5. Verificar Logs + +```bash +# Logs em tempo real +tail -f ~/opencode-shares/logs/$(date +%Y-%m-%d).log + +# Ver todos os logs +cat ~/opencode-shares/logs/*.log +``` + +## 🔧 Integração com OpenCode + +### Teste Completo + +```bash +# 1. Configurar cliente +echo '{"enterprise":{"url":"http://localhost:3000"}}' > ~/.opencode/opencode.json + +# 2. Criar sessão no OpenCode (em outro terminal) +cd ../opencode +bun dev run "Teste de compartilhamento local" + +# 3. Verificar sincronização +ls ~/opencode-shares/data/share/ +cat ~/opencode-shares/data/share/*.json + +# 4. Visualizar na web +open http://localhost:3000 +``` + +## 📊 Estrutura de Dados + +``` +~/opencode-shares/ +├── data/ +│ ├── share/ # Informações dos shares +│ ├── share_event/ # Eventos de sincronização +│ └── share_compaction/ # Dados compactados +└── logs/ + └── YYYY-MM-DD.log # Logs diários +``` + +## 🎨 Interface Web + +### Páginas Disponíveis + +- `/` - Lista de todos os compartilhamentos (requer login) +- `/share/:id` - Detalhes de um compartilhamento específico +- `/login` - Página de login +- `/health` - Status do servidor + +### Credenciais Padrão + +- **Usuário:** admin +- **Senha:** opencode + +⚠️ **Altere a senha em produção!** + +```bash +SHARE_AUTH_PASSWORD=minhasenha123 bun dev +``` + +## 🔐 Segurança + +### Desabilitar Autenticação (não recomendado) + +```bash +SHARE_AUTH_ENABLED=false bun dev +``` + +### Restringir Acesso Local Apenas + +```bash +HOST=127.0.0.1 bun dev +``` + +### Alterar TTL + +```bash +# 30 dias +SHARE_TTL_DAYS=30 bun dev + +# Desabilitar TTL +SHARE_TTL_ENABLED=false bun dev +``` + +## 📝 Logs de Exemplo + +``` +[2026-01-21T02:27:05.737Z] [INFO] User logged in: admin +[2026-01-21T02:27:12.800Z] [INFO] Share created: abc12345 for session xyz... +[2026-01-21T02:27:12.800Z] [ACCESS] 192.168.1.50 POST /api/share 200 6ms +[2026-01-21T02:27:16.941Z] [INFO] Share data retrieved: abc12345 (15 items) +[2026-01-21T02:27:16.941Z] [ACCESS] 192.168.1.50 GET /api/share/abc12345/data 200 2ms +``` + +## 🐛 Troubleshooting + +### Porta em Uso + +```bash +# Verificar +lsof -i :3000 + +# Usar outra porta +PORT=8080 bun dev +``` + +### Cliente Não Conecta + +```bash +# 1. Testar servidor +curl http://localhost:3000/health + +# 2. Verificar config do cliente +cat ~/.opencode/opencode.json + +# 3. Verificar firewall +sudo ufw allow 3000/tcp +``` + +### Permissões + +```bash +# Criar diretórios manualmente +mkdir -p ~/opencode-shares/{data,logs} +chmod 755 ~/opencode-shares +``` + +## 🎯 Próximos Passos + +1. ✅ Servidor está funcionando perfeitamente +2. ✅ Testes de API passaram +3. ✅ Storage local criado corretamente +4. ✅ Logs sendo gerados +5. ⬜ Testar com cliente OpenCode real +6. ⬜ Testar acesso de outro dispositivo na rede +7. ⬜ Configurar senha personalizada + +## 📚 Documentação Completa + +Veja [README.md](./README.md) para documentação completa. + +## ✨ Features Implementadas + +- API REST compatível com OpenCode enterprise +- Storage filesystem (não usa S3/R2) +- Autenticação com sessão em cookies +- Interface web responsiva +- Logging com rotação diária +- TTL automático configurável +- Compactação inteligente de dados +- Health check endpoint +- CORS habilitado +- Tratamento de erros completo + +--- + +**Status:** ✅ PRONTO PARA USO + +**Última atualização:** 2026-01-21 diff --git a/packages/share-server/README.md b/packages/share-server/README.md new file mode 100644 index 000000000000..771dea664955 --- /dev/null +++ b/packages/share-server/README.md @@ -0,0 +1,334 @@ +# OpenCode Share Server + +Servidor local de compartilhamento de sessões do OpenCode. Permite compartilhar sessões na rede privada local sem enviar dados para servidores remotos públicos. + +## 🚀 Início Rápido + +### 1. Instalação + +```bash +cd packages/share-server +bun install +``` + +### 2. Configuração (Opcional) + +Copie o arquivo de exemplo e edite conforme necessário: + +```bash +cp .env.example .env +# Edite .env com suas preferências +``` + +### 3. Iniciar Servidor + +```bash +# Modo desenvolvimento (com hot reload) +bun dev + +# Modo produção +bun start +``` + +O servidor estará disponível em: +- **Local:** http://localhost:3000 +- **Rede:** http://SEU_IP:3000 + +## ⚙️ Configuração + +### Variáveis de Ambiente + +| Variável | Padrão | Descrição | +|----------|--------|-----------| +| `PORT` | `3000` | Porta do servidor | +| `HOST` | `0.0.0.0` | Host (0.0.0.0 = rede local, 127.0.0.1 = apenas local) | +| `SHARE_DATA_DIR` | `~/opencode-shares/data` | Diretório de armazenamento dos dados | +| `SHARE_LOG_DIR` | `~/opencode-shares/logs` | Diretório de logs | +| `SHARE_AUTH_ENABLED` | `true` | Habilitar autenticação | +| `SHARE_AUTH_USERNAME` | `admin` | Usuário para login | +| `SHARE_AUTH_PASSWORD` | `opencode` | Senha para login | +| `SHARE_TTL_ENABLED` | `true` | Habilitar auto-delete de shares antigos | +| `SHARE_TTL_DAYS` | `7` | Dias antes de deletar shares automaticamente | + +### Configurar Cliente OpenCode + +Para que o OpenCode use o servidor local, edite o arquivo de configuração: + +**Arquivo:** `~/.opencode/opencode.json` ou `.opencode/opencode.jsonc` + +```json +{ + "enterprise": { + "url": "http://localhost:3000" + } +} +``` + +Se estiver usando de outra máquina na rede: + +```json +{ + "enterprise": { + "url": "http://192.168.1.100:3000" + } +} +``` + +## 📡 Endpoints da API + +### Criar Compartilhamento +```bash +POST /api/share +Content-Type: application/json + +{ + "sessionID": "abc123..." +} + +Response: +{ + "id": "abc12345", + "secret": "uuid-here", + "url": "http://localhost:3000/share/abc12345" +} +``` + +### Sincronizar Dados +```bash +POST /api/share/:shareID/sync +Content-Type: application/json + +{ + "secret": "uuid-here", + "data": [...] +} +``` + +### Obter Dados +```bash +GET /api/share/:shareID/data + +Response: [...] +``` + +### Deletar Compartilhamento +```bash +DELETE /api/share/:shareID +Content-Type: application/json + +{ + "secret": "uuid-here" +} +``` + +## 🌐 Interface Web + +Acesse http://localhost:3000 no navegador para: + +- ✅ Ver lista de todos os compartilhamentos +- ✅ Visualizar detalhes de cada compartilhamento +- ✅ Ver estatísticas (sessions, messages, parts, diffs) +- ✅ Login/logout com autenticação + +### Credenciais Padrão + +- **Usuário:** `admin` +- **Senha:** `opencode` + +⚠️ **Importante:** Altere a senha em produção via variáveis de ambiente! + +## 🧪 Testes + +### Teste Manual com cURL + +```bash +# 1. Criar compartilhamento +curl -X POST http://localhost:3000/api/share \ + -H "Content-Type: application/json" \ + -d '{"sessionID":"test_12345678"}' + +# Resposta exemplo: +# {"id":"12345678","secret":"","url":"http://localhost:3000/share/12345678"} + +# 2. Obter dados +curl http://localhost:3000/api/share/12345678/data + +# 3. Health check +curl http://localhost:3000/health +``` + +### Teste com OpenCode Cliente + +```bash +# 1. Configurar cliente +echo '{"enterprise":{"url":"http://localhost:3000"}}' > ~/.opencode/opencode.json + +# 2. Criar sessão +cd ../../packages/opencode +bun dev run "Olá mundo" + +# 3. Verificar sincronização +ls ~/opencode-shares/data/share/ +cat ~/opencode-shares/data/share/*.json + +# 4. Ver logs +tail -f ~/opencode-shares/logs/$(date +%Y-%m-%d).log + +# 5. Visualizar na web +open http://localhost:3000 +``` + +## 📂 Estrutura de Armazenamento + +``` +~/opencode-shares/ +├── data/ +│ ├── share/ +│ │ ├── abc12345.json # Info do share +│ │ └── xyz67890.json +│ ├── share_event/ +│ │ ├── abc12345/ +│ │ │ ├── 9999999999999.json # Eventos de sincronização +│ │ │ └── 9999999999998.json +│ │ └── xyz67890/ +│ └── share_compaction/ +│ ├── abc12345.json # Dados compactados +│ └── xyz67890.json +└── logs/ + ├── 2025-01-20.log # Logs por dia + └── 2025-01-21.log +``` + +## 🔐 Segurança + +### Autenticação + +- **Padrão:** Habilitada com usuário/senha +- **Desabilitar:** `SHARE_AUTH_ENABLED=false` (não recomendado em rede) +- **Sessions:** Armazenadas em memória (resetam ao reiniciar servidor) +- **Duração:** 24 horas + +### Rede + +- **0.0.0.0:** Acessível de qualquer dispositivo na rede local +- **127.0.0.1:** Apenas acesso local (mais seguro) +- **HTTPS:** Não implementado (use proxy reverso como nginx se necessário) + +### Dados + +- **Storage:** Filesystem local em `~/opencode-shares/` +- **Secret:** UUID aleatório por compartilhamento +- **TTL:** Auto-delete configurável (padrão 7 dias) + +## ♻️ Limpeza Automática (TTL) + +O servidor verifica a cada hora e remove automaticamente compartilhamentos com mais de N dias (configurável). + +```bash +# Desabilitar TTL +SHARE_TTL_ENABLED=false + +# Alterar período (30 dias) +SHARE_TTL_DAYS=30 +``` + +## 📊 Logs + +Logs são salvos em arquivos diários com formato: + +``` +[2025-01-20T10:30:45.123Z] [INFO] Share created: abc12345 for session xyz... +[2025-01-20T10:30:46.456Z] [ACCESS] 192.168.1.50 POST /api/share 200 15ms +``` + +### Ver Logs + +```bash +# Logs do dia atual +tail -f ~/opencode-shares/logs/$(date +%Y-%m-%d).log + +# Todos os logs +cat ~/opencode-shares/logs/*.log + +# Filtrar erros +grep ERROR ~/opencode-shares/logs/*.log +``` + +## 🐛 Troubleshooting + +### Servidor não inicia + +```bash +# Verificar se a porta está em uso +lsof -i :3000 + +# Usar outra porta +PORT=8080 bun dev +``` + +### Cliente não conecta + +```bash +# 1. Verificar se servidor está rodando +curl http://localhost:3000/health + +# 2. Verificar configuração do cliente +cat ~/.opencode/opencode.json + +# 3. Testar de outro dispositivo (usar IP da máquina) +curl http://192.168.1.100:3000/health +``` + +### Permissões de diretório + +```bash +# Criar diretórios manualmente se houver erro +mkdir -p ~/opencode-shares/data ~/opencode-shares/logs +chmod 755 ~/opencode-shares +``` + +### Logs não aparecem + +```bash +# Verificar variável de ambiente +echo $SHARE_LOG_DIR + +# Verificar permissões +ls -la ~/opencode-shares/logs/ +``` + +## 🎯 Features + +### Implementadas + +- ✅ API REST compatível com OpenCode +- ✅ Storage filesystem local +- ✅ Autenticação com usuário/senha +- ✅ Interface web para visualização +- ✅ Logging detalhado +- ✅ TTL e auto-delete de shares antigos +- ✅ Acesso na rede local (LAN) +- ✅ Compactação inteligente de dados +- ✅ Health check endpoint + +### Futuras (Opcionais) + +- ⬜ HTTPS com certificado auto-assinado +- ⬜ Export/import de shares como .zip +- ⬜ Autenticação multi-usuário +- ⬜ Persistência de sessions em arquivo +- ⬜ Rate limiting +- ⬜ Métricas e dashboard + +## 📄 Licença + +Mesmo do OpenCode (MIT) + +## 🤝 Contribuindo + +Este é um servidor standalone independente. Para contribuir com o OpenCode principal, veja [CONTRIBUTING.md](../../CONTRIBUTING.md). + +## 📞 Suporte + +- Issues: https://github.com/anomalyco/opencode/issues +- Documentação: https://opencode.ai/docs diff --git a/packages/share-server/package.json b/packages/share-server/package.json new file mode 100644 index 000000000000..22973ad1efb8 --- /dev/null +++ b/packages/share-server/package.json @@ -0,0 +1,20 @@ +{ + "name": "@opencode-ai/share-server", + "version": "0.1.0", + "description": "Servidor local de compartilhamento de sessões do OpenCode", + "type": "module", + "scripts": { + "dev": "bun run --hot src/index.ts", + "start": "bun run src/index.ts" + }, + "dependencies": { + "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/util": "workspace:*", + "hono": "catalog:", + "zod": "catalog:" + }, + "devDependencies": { + "@types/bun": "catalog:", + "typescript": "catalog:" + } +} diff --git a/packages/share-server/src/auth.ts b/packages/share-server/src/auth.ts new file mode 100644 index 000000000000..1c42ecd82f2f --- /dev/null +++ b/packages/share-server/src/auth.ts @@ -0,0 +1,111 @@ +import type { Context, Next } from "hono" +import { getCookie, setCookie, deleteCookie } from "hono/cookie" +import { logger } from "./logger" + +const AUTH_USERNAME = process.env.SHARE_AUTH_USERNAME || "admin" +const AUTH_PASSWORD = process.env.SHARE_AUTH_PASSWORD || "opencode" +const AUTH_ENABLED = process.env.SHARE_AUTH_ENABLED !== "false" + +// Session storage simples (em memória) +const sessions = new Map() + +export async function requireAuth(c: Context, next: Next) { + if (!AUTH_ENABLED) { + return next() + } + + const sessionId = getCookie(c, "session") + + if (!sessionId) { + logger.warn(`Unauthenticated access attempt to ${c.req.path}`) + return c.redirect("/login") + } + + const session = sessions.get(sessionId) + + if (!session || session.expires < Date.now()) { + sessions.delete(sessionId) + deleteCookie(c, "session") + logger.warn(`Expired session attempt: ${sessionId.substring(0, 8)}...`) + return c.redirect("/login") + } + + return next() +} + +export function login(username: string, password: string): string | null { + if (username === AUTH_USERNAME && password === AUTH_PASSWORD) { + const sessionId = crypto.randomUUID() + sessions.set(sessionId, { + username, + expires: Date.now() + 24 * 60 * 60 * 1000, // 24h + }) + logger.info(`User logged in: ${username}`) + return sessionId + } + logger.warn(`Failed login attempt for user: ${username}`) + return null +} + +export function logout(sessionId: string) { + const session = sessions.get(sessionId) + if (session) { + logger.info(`User logged out: ${session.username}`) + } + sessions.delete(sessionId) +} + +export async function loginHandler(c: Context) { + const body = await c.req.parseBody() + const username = body.username as string + const password = body.password as string + + const sessionId = login(username, password) + + if (sessionId) { + setCookie(c, "session", sessionId, { + maxAge: 24 * 60 * 60, // 24h + httpOnly: true, + sameSite: "Lax", + }) + return c.redirect("/") + } + + return c.html(` + + + + Login Failed + + + + +
+

❌ Login Failed

+

Usuário ou senha incorretos.

+

Tentar novamente

+
+ + + `) +} + +export async function logoutHandler(c: Context) { + const sessionId = getCookie(c, "session") + if (sessionId) { + logout(sessionId) + } + deleteCookie(c, "session") + return c.redirect("/login") +} + +// Log configuração de autenticação ao inicializar +if (AUTH_ENABLED) { + console.log(`🔐 Authentication: ENABLED (username: ${AUTH_USERNAME})`) +} else { + console.log(`🔓 Authentication: DISABLED`) +} diff --git a/packages/share-server/src/cleanup.ts b/packages/share-server/src/cleanup.ts new file mode 100644 index 000000000000..4d2834907e0e --- /dev/null +++ b/packages/share-server/src/cleanup.ts @@ -0,0 +1,46 @@ +import { Share } from "./share" +import { logger } from "./logger" + +const TTL_DAYS = parseInt(process.env.SHARE_TTL_DAYS || "7") +const TTL_ENABLED = process.env.SHARE_TTL_ENABLED !== "false" +const CLEANUP_INTERVAL_MS = 60 * 60 * 1000 // 1 hora + +async function cleanupOldShares() { + if (!TTL_ENABLED) return + + const cutoff = Date.now() - TTL_DAYS * 24 * 60 * 60 * 1000 + const shares = await Share.list() + + let deletedCount = 0 + + for (const share of shares) { + if (share.createdAt && share.createdAt < cutoff) { + try { + await Share.remove({ id: share.id, secret: share.secret }) + logger.info(`Auto-deleted expired share: ${share.id} (created: ${new Date(share.createdAt).toISOString()})`) + deletedCount++ + } catch (error) { + logger.error(`Failed to delete share ${share.id}: ${error}`) + } + } + } + + if (deletedCount > 0) { + logger.info(`Cleanup completed: deleted ${deletedCount} expired shares`) + } +} + +export function startCleanupScheduler() { + if (!TTL_ENABLED) { + console.log(`♻️ TTL Cleanup: DISABLED`) + return + } + + console.log(`♻️ TTL Cleanup: ENABLED (${TTL_DAYS} days, checking every hour)`) + + // Executar imediatamente na inicialização + cleanupOldShares() + + // Agendar execuções periódicas + setInterval(cleanupOldShares, CLEANUP_INTERVAL_MS) +} diff --git a/packages/share-server/src/index.ts b/packages/share-server/src/index.ts new file mode 100644 index 000000000000..17250e01bb10 --- /dev/null +++ b/packages/share-server/src/index.ts @@ -0,0 +1,46 @@ +import app from "./server" +import { startCleanupScheduler } from "./cleanup" + +const PORT = parseInt(process.env.PORT || "3000") +const HOST = process.env.HOST || "0.0.0.0" + +console.log(` +┌─────────────────────────────────────────────────────────────┐ +│ │ +│ 🚀 OpenCode Share Server v0.1.0 │ +│ │ +│ Servidor de compartilhamento local de sessões │ +│ │ +└─────────────────────────────────────────────────────────────┘ +`) + +// Iniciar scheduler de limpeza (TTL) +startCleanupScheduler() + +// Iniciar servidor +const server = Bun.serve({ + port: PORT, + hostname: HOST, + fetch: app.fetch, +}) + +console.log(` +✅ Servidor rodando em: + - Local: http://localhost:${PORT} + - Network: http://${HOST}:${PORT} + +📖 Endpoints disponíveis: + - POST /api/share (criar compartilhamento) + - POST /api/share/:id/sync (sincronizar dados) + - GET /api/share/:id/data (obter dados) + - DELETE /api/share/:id (deletar compartilhamento) + - GET / (interface web) + - GET /share/:id (visualizar compartilhamento) + - GET /health (health check) + +🔧 Configuração: + - Autenticação: ${process.env.SHARE_AUTH_ENABLED !== "false" ? "ATIVADA" : "DESATIVADA"} + - TTL Cleanup: ${process.env.SHARE_TTL_ENABLED !== "false" ? `ATIVADO (${process.env.SHARE_TTL_DAYS || "7"} dias)` : "DESATIVADO"} + +Para parar o servidor, pressione Ctrl+C +`) diff --git a/packages/share-server/src/logger.ts b/packages/share-server/src/logger.ts new file mode 100644 index 000000000000..3f66b47f0b9b --- /dev/null +++ b/packages/share-server/src/logger.ts @@ -0,0 +1,37 @@ +import { appendFile, mkdir } from "fs/promises" +import path from "path" +import { homedir } from "os" + +const LOG_DIR = + process.env.SHARE_LOG_DIR?.replace("~", homedir()) || path.join(homedir(), "opencode-shares", "logs") + +async function ensureLogDir() { + await mkdir(LOG_DIR, { recursive: true }) +} + +export const logger = { + info: (msg: string) => log("INFO", msg), + error: (msg: string) => log("ERROR", msg), + warn: (msg: string) => log("WARN", msg), + access: (ip: string, method: string, urlPath: string, status: number, duration: number) => { + log("ACCESS", `${ip} ${method} ${urlPath} ${status} ${duration}ms`) + }, +} + +async function log(level: string, msg: string) { + const timestamp = new Date().toISOString() + const line = `[${timestamp}] [${level}] ${msg}\n` + + console.log(line.trim()) + + try { + await ensureLogDir() + const logFile = path.join(LOG_DIR, `${new Date().toISOString().split("T")[0]}.log`) + await appendFile(logFile, line) + } catch (error) { + console.error("Failed to write log:", error) + } +} + +// Log do diretório ao inicializar +console.log(`📝 Logs directory: ${LOG_DIR}`) diff --git a/packages/share-server/src/server.ts b/packages/share-server/src/server.ts new file mode 100644 index 000000000000..764ee1a57028 --- /dev/null +++ b/packages/share-server/src/server.ts @@ -0,0 +1,134 @@ +import { Hono } from "hono" +import { cors } from "hono/cors" +import { Share } from "./share" +import { requireAuth, loginHandler, logoutHandler } from "./auth" +import { logger } from "./logger" +import { renderShareList, renderShareView, renderLogin } from "./templates" + +const app = new Hono() + +// Middleware: CORS +app.use("*", cors()) + +// Middleware: Logging +app.use("*", async (c, next) => { + const start = Date.now() + await next() + const duration = Date.now() - start + + const ip = c.req.header("x-forwarded-for") || c.req.header("x-real-ip") || "unknown" + logger.access(ip, c.req.method, c.req.path, c.res.status, duration) +}) + +// ============================================ +// API Routes (para cliente OpenCode) +// ============================================ + +app.post("/api/share", async (c) => { + try { + const body = await c.req.json() + const share = await Share.create({ sessionID: body.sessionID }) + const protocol = c.req.header("x-forwarded-proto") ?? "http" + const host = c.req.header("host") || "localhost:3000" + + logger.info(`Share created: ${share.id} for session ${share.sessionID}`) + + return c.json({ + id: share.id, + secret: share.secret, + url: `${protocol}://${host}/share/${share.id}`, + }) + } catch (error: any) { + logger.error(`Failed to create share: ${error.message}`) + return c.json({ error: error.message }, 400) + } +}) + +app.post("/api/share/:shareID/sync", async (c) => { + try { + const { shareID } = c.req.param() + const body = await c.req.json() + + await Share.sync({ + share: { id: shareID, secret: body.secret }, + data: body.data, + }) + + logger.info(`Share synced: ${shareID} (${body.data.length} items)`) + return c.json({}) + } catch (error: any) { + logger.error(`Failed to sync share: ${error.message}`) + return c.json({ error: error.message }, 400) + } +}) + +app.get("/api/share/:shareID/data", async (c) => { + try { + const { shareID } = c.req.param() + const data = await Share.data(shareID) + + logger.info(`Share data retrieved: ${shareID} (${data.length} items)`) + return c.json(data) + } catch (error: any) { + logger.error(`Failed to get share data: ${error.message}`) + return c.json({ error: error.message }, 400) + } +}) + +app.delete("/api/share/:shareID", async (c) => { + try { + const { shareID } = c.req.param() + const body = await c.req.json() + + await Share.remove({ id: shareID, secret: body.secret }) + + logger.info(`Share deleted: ${shareID}`) + return c.json({}) + } catch (error: any) { + logger.error(`Failed to delete share: ${error.message}`) + return c.json({ error: error.message }, 400) + } +}) + +// ============================================ +// Web UI Routes +// ============================================ + +app.get("/", requireAuth, async (c) => { + try { + const shares = await Share.list() + return c.html(renderShareList(shares)) + } catch (error: any) { + logger.error(`Failed to list shares: ${error.message}`) + return c.html(`

Erro ao carregar compartilhamentos

${error.message}

`, 500) + } +}) + +app.get("/share/:id", requireAuth, async (c) => { + try { + const { id } = c.req.param() + const data = await Share.data(id) + return c.html(renderShareView(id, data)) + } catch (error: any) { + logger.error(`Failed to view share: ${error.message}`) + return c.html(`

Erro ao carregar compartilhamento

${error.message}

`, 500) + } +}) + +app.get("/login", async (c) => { + return c.html(await renderLogin()) +}) + +app.post("/auth/login", loginHandler) + +app.post("/auth/logout", logoutHandler) + +// ============================================ +// Health Check +// ============================================ + +app.get("/health", (c) => { + return c.json({ status: "ok", timestamp: new Date().toISOString() }) +}) + +export default app diff --git a/packages/share-server/src/share.ts b/packages/share-server/src/share.ts new file mode 100644 index 000000000000..4a169f0fd5c7 --- /dev/null +++ b/packages/share-server/src/share.ts @@ -0,0 +1,165 @@ +import { FileDiff, Message, Model, Part, Session } from "@opencode-ai/sdk/v2" +import { fn } from "@opencode-ai/util/fn" +import { iife } from "@opencode-ai/util/iife" +import { Identifier } from "@opencode-ai/util/identifier" +import { Binary } from "@opencode-ai/util/binary" +import z from "zod" +import { Storage } from "./storage-fs" + +export namespace Share { + export const Info = z.object({ + id: z.string(), + secret: z.string(), + sessionID: z.string(), + createdAt: z.number().optional(), + }) + export type Info = z.infer + + export const Data = z.discriminatedUnion("type", [ + z.object({ + type: z.literal("session"), + data: z.custom(), + }), + z.object({ + type: z.literal("message"), + data: z.custom(), + }), + z.object({ + type: z.literal("part"), + data: z.custom(), + }), + z.object({ + type: z.literal("session_diff"), + data: z.custom(), + }), + z.object({ + type: z.literal("model"), + data: z.custom(), + }), + ]) + export type Data = z.infer + + export const create = fn(z.object({ sessionID: z.string() }), async (body) => { + const isTest = process.env.NODE_ENV === "test" || body.sessionID.startsWith("test_") + const info: Info = { + id: (isTest ? "test_" : "") + body.sessionID.slice(-8), + sessionID: body.sessionID, + secret: crypto.randomUUID(), + createdAt: Date.now(), + } + const exists = await get(info.id) + if (exists) throw new Errors.AlreadyExists(info.id) + await Storage.write(["share", info.id], info) + return info + }) + + export async function get(id: string) { + return Storage.read(["share", id]) + } + + export async function list() { + const shares = await Storage.list({ prefix: ["share"] }) + const result: Info[] = [] + for (const key of shares) { + const info = await Storage.read(key) + if (info) result.push(info) + } + return result.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)) + } + + export const remove = fn(Info.pick({ id: true, secret: true }), async (body) => { + const share = await get(body.id) + if (!share) throw new Errors.NotFound(body.id) + if (share.secret !== body.secret) throw new Errors.InvalidSecret(body.id) + await Storage.remove(["share", body.id]) + const listEvents = await Storage.list({ prefix: ["share_event", body.id] }) + for (const item of listEvents) { + await Storage.remove(item) + } + await Storage.remove(["share_compaction", body.id]) + }) + + export const sync = fn( + z.object({ + share: Info.pick({ id: true, secret: true }), + data: Data.array(), + }), + async (input) => { + const share = await get(input.share.id) + if (!share) throw new Errors.NotFound(input.share.id) + if (share.secret !== input.share.secret) throw new Errors.InvalidSecret(input.share.id) + await Storage.write(["share_event", input.share.id, Identifier.descending()], input.data) + }, + ) + + type Compaction = { + event?: string + data: Data[] + } + + export async function data(shareID: string) { + console.log("reading compaction") + const compaction: Compaction = (await Storage.read(["share_compaction", shareID])) ?? { + data: [], + event: undefined, + } + console.log("reading pending events") + const list = await Storage.list({ + prefix: ["share_event", shareID], + before: compaction.event, + }).then((x) => x.toReversed()) + + console.log("compacting", list.length) + + if (list.length > 0) { + const dataArray = await Promise.all(list.map(async (event) => await Storage.read(event))).then((x) => + x.flat(), + ) + for (const item of dataArray) { + if (!item) continue + const key = (item: Data) => { + switch (item.type) { + case "session": + return "session" + case "message": + return `message/${item.data.id}` + case "part": + return `${item.data.messageID}/${item.data.id}` + case "session_diff": + return "session_diff" + case "model": + return "model" + } + } + const id = key(item) + const result = Binary.search(compaction.data, id, key) + if (result.found) { + compaction.data[result.index] = item + } else { + compaction.data.splice(result.index, 0, item) + } + } + compaction.event = list.at(-1)?.at(-1) + await Storage.write(["share_compaction", shareID], compaction) + } + return compaction.data + } + + export const Errors = { + NotFound: class extends Error { + constructor(public id: string) { + super(`Share not found: ${id}`) + } + }, + InvalidSecret: class extends Error { + constructor(public id: string) { + super(`Share secret invalid: ${id}`) + } + }, + AlreadyExists: class extends Error { + constructor(public id: string) { + super(`Share already exists: ${id}`) + } + }, + } +} diff --git a/packages/share-server/src/storage-fs.ts b/packages/share-server/src/storage-fs.ts new file mode 100644 index 000000000000..e8e9ec6911b7 --- /dev/null +++ b/packages/share-server/src/storage-fs.ts @@ -0,0 +1,139 @@ +import { mkdir, readdir, unlink } from "fs/promises" +import path from "path" +import { homedir } from "os" + +export namespace Storage { + export interface Adapter { + read(path: string): Promise + write(path: string, value: string): Promise + remove(path: string): Promise + list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise + } + + const baseDir = + process.env.SHARE_DATA_DIR?.replace("~", homedir()) || path.join(homedir(), "opencode-shares", "data") + + async function ensureDir(filePath: string) { + const dir = path.dirname(filePath) + await mkdir(dir, { recursive: true }) + } + + function resolvePath(p: string): string { + return path.join(baseDir, p) + } + + const adapter: Adapter = { + async read(filePath: string): Promise { + const fullPath = resolvePath(filePath) + const file = Bun.file(fullPath) + const exists = await file.exists() + if (!exists) return undefined + return file.text() + }, + + async write(filePath: string, value: string): Promise { + const fullPath = resolvePath(filePath) + await ensureDir(fullPath) + await Bun.write(fullPath, value) + }, + + async remove(filePath: string): Promise { + const fullPath = resolvePath(filePath) + try { + await unlink(fullPath) + } catch (error: any) { + if (error.code !== "ENOENT") throw error + } + }, + + async list(options?: { + prefix?: string + limit?: number + after?: string + before?: string + }): Promise { + const prefix = options?.prefix || "" + const prefixPath = resolvePath(prefix) + + let entries: string[] = [] + + try { + const dirPath = path.dirname(prefixPath) + const dirEntries = await readdir(dirPath, { recursive: true, withFileTypes: true }) + + entries = dirEntries + .filter((entry) => entry.isFile() && entry.name.endsWith(".json")) + .map((entry) => { + const fullPath = path.join(entry.parentPath || entry.path, entry.name) + return path.relative(baseDir, fullPath).replace(/\.json$/, "") + }) + .filter((p) => p.startsWith(prefix)) + .sort() + } catch (error: any) { + if (error.code !== "ENOENT") throw error + } + + if (options?.after) { + const afterPath = prefix + options.after + entries = entries.filter((p) => p > afterPath) + } + + if (options?.before) { + const beforePath = prefix + options.before + entries = entries.filter((p) => p < beforePath) + } + + if (options?.limit) { + entries = entries.slice(0, options.limit) + } + + return entries + }, + } + + function resolve(key: string[]) { + return key.join("/") + ".json" + } + + export async function read(key: string[]) { + const result = await adapter.read(resolve(key)) + if (!result) return undefined + return JSON.parse(result) as T + } + + export function write(key: string[], value: T) { + return adapter.write(resolve(key), JSON.stringify(value, null, 2)) + } + + export function remove(key: string[] | string) { + const path = Array.isArray(key) ? resolve(key) : key + return adapter.remove(path) + } + + export async function list(options?: { + prefix?: string[] + limit?: number + after?: string + before?: string + }) { + const p = options?.prefix ? options.prefix.join("/") + (options.prefix.length ? "/" : "") : "" + const result = await adapter.list({ + prefix: p, + limit: options?.limit, + after: options?.after, + before: options?.before, + }) + return result.map((x) => x.split("/")) + } + + export async function update(key: string[], fn: (draft: T) => void) { + const val = await read(key) + if (!val) throw new Error("Not found") + fn(val) + await write(key, val) + return val + } + + // Log do diretório base ao inicializar + console.log(`📂 Storage base directory: ${baseDir}`) +} diff --git a/packages/share-server/src/templates.ts b/packages/share-server/src/templates.ts new file mode 100644 index 000000000000..3dc95ae1c171 --- /dev/null +++ b/packages/share-server/src/templates.ts @@ -0,0 +1,324 @@ +import { Share } from "./share" + +export function renderLogin() { + return Bun.file(`${import.meta.dir}/templates/login.html`).text() +} + +export function renderShareList(shares: Share.Info[]) { + const shareRows = shares + .map( + (share) => ` + + ${share.id} + ${share.sessionID} + ${share.createdAt ? new Date(share.createdAt).toLocaleString("pt-BR") : "N/A"} + + 👁️ Ver + + + `, + ) + .join("") + + return ` + + + + + + Compartilhamentos - OpenCode + + + +
+

📦 Compartilhamentos OpenCode

+
+ +
+
+
+
+ Total de compartilhamentos: ${shares.length} +
+ ${ + shares.length > 0 + ? ` + + + + + + + + + + + ${shareRows} + +
IDSession IDCriado emAções
+ ` + : ` +
+

📭 Nenhum compartilhamento encontrado

+

Compartilhamentos criados pelo OpenCode aparecerão aqui.

+
+ ` + } +
+ + + ` +} + +export function renderShareView(shareId: string, data: Share.Data[]) { + const sessions = data.filter((d) => d.type === "session") + const messages = data.filter((d) => d.type === "message") + const parts = data.filter((d) => d.type === "part") + const diffs = data.filter((d) => d.type === "session_diff") + + const renderData = (item: Share.Data) => { + switch (item.type) { + case "session": + return `
${JSON.stringify(item.data, null, 2)}
` + case "message": + return ` +
+ Message ID: ${item.data.id}
+ Role: ${item.data.role}
+ Model: ${item.data.modelID || "N/A"}
+ Created: ${new Date(item.data.createdAt).toLocaleString("pt-BR")} +
+ ` + case "part": + return ` +
+ Part ID: ${item.data.id}
+ Message ID: ${item.data.messageID}
+ Type: ${item.data.type}
+ ${item.data.type === "text" ? `
${item.data.text || ""}
` : ""} +
+ ` + case "session_diff": + return `
${item.data.length} file diff(s)
` + default: + return `
${JSON.stringify(item, null, 2)}
` + } + } + + const dataBlocks = data.map((item) => `
${renderData(item)}
`).join("") + + return ` + + + + + + Share ${shareId} - OpenCode + + + +
+

📄 Share: ${shareId}

+ ← Voltar +
+
+
+
+

${sessions.length}

+

Sessions

+
+
+

${messages.length}

+

Messages

+
+
+

${parts.length}

+

Parts

+
+
+

${diffs.length}

+

Diffs

+
+
+

Dados Compartilhados

+ ${dataBlocks || '

Nenhum dado disponível

'} +
+ + + ` +} diff --git a/packages/share-server/src/templates/login.html b/packages/share-server/src/templates/login.html new file mode 100644 index 000000000000..048ac7440170 --- /dev/null +++ b/packages/share-server/src/templates/login.html @@ -0,0 +1,101 @@ + + + + + + Login - OpenCode Share Server + + + +
+

🔐 OpenCode Share Server

+

Faça login para acessar os compartilhamentos

+
+
+ + +
+
+ + +
+ +
+ +
+ + diff --git a/packages/share-server/tsconfig.json b/packages/share-server/tsconfig.json new file mode 100644 index 000000000000..054de37bd40b --- /dev/null +++ b/packages/share-server/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext"], + "moduleResolution": "bundler", + "types": ["bun-types"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} From 0552a7d508f8fc555bce6db4efcfa690f7dc7bc0 Mon Sep 17 00:00:00 2001 From: Rodrigo Tornis Date: Wed, 4 Feb 2026 15:25:00 -0300 Subject: [PATCH 2/2] chore: remove outdated GitHub workflows - Deleted generate.yml workflow for code generation. - Removed nix-desktop.yml workflow for Nix desktop builds. - Eliminated notify-discord.yml workflow for Discord notifications. - Removed opencode.yml workflow for handling opencode commands. - Deleted pr-standards.yml workflow for PR title and issue checks. - Removed publish-github-action.yml workflow for publishing GitHub actions. - Deleted publish-vscode.yml workflow for publishing VSCode extensions. - Eliminated publish.yml workflow for general publishing tasks. - Removed release-github-action.yml workflow for GitHub release actions. - Deleted review.yml workflow for PR review guidelines. - Removed stale-issues.yml workflow for auto-closing stale issues. - Deleted stats.yml workflow for daily stats updates. - Eliminated sync-zed-extension.yml workflow for syncing Zed extensions. - Removed test.yml workflow for running tests. - Deleted triage.yml workflow for issue triage. - Removed typecheck.yml workflow for type checking. - Deleted update-nix-hashes.yml workflow for updating Nix hashes. --- .github/CODEOWNERS | 4 - .github/ISSUE_TEMPLATE/bug-report.yml | 67 ------ .github/ISSUE_TEMPLATE/config.yml | 5 - .github/ISSUE_TEMPLATE/feature-request.yml | 20 -- .github/ISSUE_TEMPLATE/question.yml | 11 - .github/actions/setup-bun/action.yml | 22 -- .github/publish-python-sdk.yml | 71 ------ .github/pull_request_template.md | 3 - .github/workflows/deploy.yml | 29 --- .github/workflows/docs-update.yml | 72 ------ .github/workflows/duplicate-issues.yml | 63 ------ .github/workflows/duplicate-prs.yml | 65 ------ .github/workflows/generate.yml | 51 ----- .github/workflows/nix-desktop.yml | 46 ---- .github/workflows/notify-discord.yml | 14 -- .github/workflows/opencode.yml | 34 --- .github/workflows/pr-standards.yml | 139 ------------ .github/workflows/publish-github-action.yml | 30 --- .github/workflows/publish-vscode.yml | 37 --- .github/workflows/publish.yml | 237 -------------------- .github/workflows/release-github-action.yml | 29 --- .github/workflows/review.yml | 83 ------- .github/workflows/stale-issues.yml | 33 --- .github/workflows/stats.yml | 35 --- .github/workflows/sync-zed-extension.yml | 35 --- .github/workflows/test.yml | 133 ----------- .github/workflows/triage.yml | 37 --- .github/workflows/typecheck.yml | 19 -- .github/workflows/update-nix-hashes.yml | 138 ------------ 29 files changed, 1562 deletions(-) delete mode 100644 .github/CODEOWNERS delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml delete mode 100644 .github/ISSUE_TEMPLATE/question.yml delete mode 100644 .github/actions/setup-bun/action.yml delete mode 100644 .github/publish-python-sdk.yml delete mode 100644 .github/pull_request_template.md delete mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/docs-update.yml delete mode 100644 .github/workflows/duplicate-issues.yml delete mode 100644 .github/workflows/duplicate-prs.yml delete mode 100644 .github/workflows/generate.yml delete mode 100644 .github/workflows/nix-desktop.yml delete mode 100644 .github/workflows/notify-discord.yml delete mode 100644 .github/workflows/opencode.yml delete mode 100644 .github/workflows/pr-standards.yml delete mode 100644 .github/workflows/publish-github-action.yml delete mode 100644 .github/workflows/publish-vscode.yml delete mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/release-github-action.yml delete mode 100644 .github/workflows/review.yml delete mode 100644 .github/workflows/stale-issues.yml delete mode 100644 .github/workflows/stats.yml delete mode 100644 .github/workflows/sync-zed-extension.yml delete mode 100644 .github/workflows/test.yml delete mode 100644 .github/workflows/triage.yml delete mode 100644 .github/workflows/typecheck.yml delete mode 100644 .github/workflows/update-nix-hashes.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 9a888adbb259..000000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# web + desktop packages -packages/app/ @adamdotdevin -packages/tauri/ @adamdotdevin -packages/desktop/ @adamdotdevin diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml deleted file mode 100644 index fe1ec8409b43..000000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Bug report -description: Report an issue that should be fixed -labels: ["bug"] -body: - - type: textarea - id: description - attributes: - label: Description - description: Describe the bug you encountered - placeholder: What happened? - validations: - required: true - - - type: input - id: plugins - attributes: - label: Plugins - description: What plugins are you using? - validations: - required: false - - - type: input - id: opencode-version - attributes: - label: OpenCode version - description: What version of OpenCode are you using? - validations: - required: false - - - type: textarea - id: reproduce - attributes: - label: Steps to reproduce - description: How can we reproduce this issue? - placeholder: | - 1. - 2. - 3. - validations: - required: false - - - type: textarea - id: screenshot-or-link - attributes: - label: Screenshot and/or share link - description: Run `/share` to get a share link, or attach a screenshot - placeholder: Paste link or drag and drop screenshot here - validations: - required: false - - - type: input - id: os - attributes: - label: Operating System - description: what OS are you using? - placeholder: e.g., macOS 26.0.1, Ubuntu 22.04, Windows 11 - validations: - required: false - - - type: input - id: terminal - attributes: - label: Terminal - description: what terminal are you using? - placeholder: e.g., iTerm2, Ghostty, Alacritty, Windows Terminal - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 459ce25d05b9..000000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: 💬 Discord Community - url: https://discord.gg/opencode - about: For quick questions or real-time discussion. Note that issues are searchable and help others with the same question. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index 92e6c47570a0..000000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: 🚀 Feature Request -description: Suggest an idea, feature, or enhancement -labels: [discussion] -title: "[FEATURE]:" - -body: - - type: checkboxes - id: verified - attributes: - label: Feature hasn't been suggested before. - options: - - label: I have verified this feature I'm about to request hasn't been suggested before. - required: true - - - type: textarea - attributes: - label: Describe the enhancement you want to request - description: What do you want to change or add? What are the benefits of implementing this? Try to be detailed so we can understand your request better :) - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml deleted file mode 100644 index 2310bfcc86b7..000000000000 --- a/.github/ISSUE_TEMPLATE/question.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Question -description: Ask a question -labels: ["question"] -body: - - type: textarea - id: question - attributes: - label: Question - description: What's your question? - validations: - required: true diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml deleted file mode 100644 index cba04faccefc..000000000000 --- a/.github/actions/setup-bun/action.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: "Setup Bun" -description: "Setup Bun with caching and install dependencies" -runs: - using: "composite" - steps: - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version-file: package.json - - - name: Cache ~/.bun - id: cache-bun - uses: actions/cache@v4 - with: - path: ~/.bun - key: ${{ runner.os }}-bun-${{ hashFiles('package.json') }}-${{ hashFiles('bun.lockb', 'bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun-${{ hashFiles('package.json') }}- - - - name: Install dependencies - run: bun install - shell: bash diff --git a/.github/publish-python-sdk.yml b/.github/publish-python-sdk.yml deleted file mode 100644 index 151ecb9944b6..000000000000 --- a/.github/publish-python-sdk.yml +++ /dev/null @@ -1,71 +0,0 @@ -# -# This file is intentionally in the wrong dir, will move and add later.... -# - -# name: publish-python-sdk - -# on: -# release: -# types: [published] -# workflow_dispatch: - -# jobs: -# publish: -# runs-on: ubuntu-latest -# permissions: -# contents: read -# steps: -# - name: Checkout repository -# uses: actions/checkout@v4 - -# - name: Setup Bun -# uses: oven-sh/setup-bun@v1 -# with: -# bun-version: 1.2.21 - -# - name: Install dependencies (JS/Bun) -# run: bun install - -# - name: Install uv -# shell: bash -# run: curl -LsSf https://astral.sh/uv/install.sh | sh - -# - name: Generate Python SDK from OpenAPI (CLI) -# shell: bash -# run: | -# ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source cli - -# - name: Sync Python dependencies -# shell: bash -# run: | -# ~/.local/bin/uv sync --dev --project packages/sdk/python - -# - name: Set version from release tag -# shell: bash -# run: | -# TAG="${GITHUB_REF_NAME:-}" -# if [ -z "$TAG" ]; then -# TAG="$(git describe --tags --abbrev=0 || echo 0.0.0)" -# fi -# echo "Using version: $TAG" -# VERSION="$TAG" ~/.local/bin/uv run --project packages/sdk/python python - <<'PY' -# import os, re, pathlib -# root = pathlib.Path('packages/sdk/python') -# pt = (root / 'pyproject.toml').read_text() -# version = os.environ.get('VERSION','0.0.0').lstrip('v') -# pt = re.sub(r'(?m)^(version\s*=\s*")[^"]+("\s*)$', f"\\1{version}\\2", pt) -# (root / 'pyproject.toml').write_text(pt) -# # Also update generator config override for consistency -# cfgp = root / 'openapi-python-client.yaml' -# if cfgp.exists(): -# cfg = cfgp.read_text() -# cfg = re.sub(r'(?m)^(package_version_override:\s*)\S+$', f"\\1{version}", cfg) -# cfgp.write_text(cfg) -# PY - -# - name: Build and publish to PyPI -# env: -# PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} -# shell: bash -# run: | -# ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index b4369fa1a43e..000000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,3 +0,0 @@ -### What does this PR do? - -### How did you verify your code works? diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 25466a63e066..000000000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: deploy - -on: - push: - branches: - - dev - - production - workflow_dispatch: - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - deploy: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - - - uses: ./.github/actions/setup-bun - - - uses: actions/setup-node@v4 - with: - node-version: "24" - - - run: bun sst deploy --stage=${{ github.ref_name }} - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }} - PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }} - STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }} diff --git a/.github/workflows/docs-update.yml b/.github/workflows/docs-update.yml deleted file mode 100644 index a8dd2ae4f2b7..000000000000 --- a/.github/workflows/docs-update.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Docs Update - -on: - schedule: - - cron: "0 */12 * * *" - workflow_dispatch: - -env: - LOOKBACK_HOURS: 4 - -jobs: - update-docs: - if: github.repository == 'sst/opencode' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - id-token: write - contents: write - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch full history to access commits - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Get recent commits - id: commits - run: | - COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "") - if [ -z "$COMMITS" ]; then - echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours" - echo "has_commits=false" >> $GITHUB_OUTPUT - else - echo "has_commits=true" >> $GITHUB_OUTPUT - { - echo "list<> $GITHUB_OUTPUT - fi - - - name: Run opencode - if: steps.commits.outputs.has_commits == 'true' - uses: sst/opencode/github@latest - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - with: - model: opencode/gpt-5.2 - agent: docs - prompt: | - Review the following commits from the last ${{ env.LOOKBACK_HOURS }} hours and identify any new features that may need documentation. - - - ${{ steps.commits.outputs.list }} - - - Steps: - 1. For each commit that looks like a new feature or significant change: - - Read the changed files to understand what was added - - Check if the feature is already documented in packages/web/src/content/docs/* - 2. If you find undocumented features: - - Update the relevant documentation files in packages/web/src/content/docs/* - - Follow the existing documentation style and structure - - Make sure to document the feature clearly with examples where appropriate - 3. If all new features are already documented, report that no updates are needed - 4. If you are creating a new documentation file be sure to update packages/web/astro.config.mjs too. - - Focus on user-facing features and API changes. Skip internal refactors, bug fixes, and test updates unless they affect user-facing behavior. - Don't feel the need to document every little thing. It is perfectly okay to make 0 changes at all. - Try to keep documentation only for large features or changes that already have a good spot to be documented. diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml deleted file mode 100644 index 53aa2a725eb7..000000000000 --- a/.github/workflows/duplicate-issues.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Duplicate Issue Detection - -on: - issues: - types: [opened] - -jobs: - check-duplicates: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Check for duplicate issues - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh issue*": "allow" - }, - "webfetch": "deny" - } - run: | - opencode run -m opencode/claude-haiku-4-5 "A new issue has been created:' - - Issue number: - ${{ github.event.issue.number }} - - Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue. - Consider: - 1. Similar titles or descriptions - 2. Same error messages or symptoms - 3. Related functionality or components - 4. Similar feature requests - - If you find any potential duplicates, please comment on the new issue with: - - A brief explanation of why it might be a duplicate - - Links to the potentially duplicate issues - - A suggestion to check those issues first - - Use this format for the comment: - 'This issue might be a duplicate of existing issues. Please check: - - #[issue_number]: [brief description of similarity] - - Feel free to ignore if none of these address your specific case.' - - Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, please add a comment mentioning the pinned keybinds issue #4997: - 'For keybind-related issues, please also check our pinned keybinds documentation: #4997' - - If no clear duplicates are found, do not comment." diff --git a/.github/workflows/duplicate-prs.yml b/.github/workflows/duplicate-prs.yml deleted file mode 100644 index 326068589581..000000000000 --- a/.github/workflows/duplicate-prs.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Duplicate PR Check - -on: - pull_request_target: - types: [opened] - -jobs: - check-duplicates: - if: | - github.event.pull_request.user.login != 'actions-user' && - github.event.pull_request.user.login != 'opencode' && - github.event.pull_request.user.login != 'rekram1-node' && - github.event.pull_request.user.login != 'thdxr' && - github.event.pull_request.user.login != 'kommander' && - github.event.pull_request.user.login != 'jayair' && - github.event.pull_request.user.login != 'fwang' && - github.event.pull_request.user.login != 'adamdotdevin' && - github.event.pull_request.user.login != 'iamdavidhill' && - github.event.pull_request.user.login != 'opencode-agent[bot]' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Install dependencies - run: bun install - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Build prompt - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - { - echo "Check for duplicate PRs related to this new PR:" - echo "" - echo "CURRENT_PR_NUMBER: $PR_NUMBER" - echo "" - echo "Title: $(gh pr view "$PR_NUMBER" --json title --jq .title)" - echo "" - echo "Description:" - gh pr view "$PR_NUMBER" --json body --jq .body - } > pr_info.txt - - - name: Check for duplicate PRs - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - COMMENT=$(bun script/duplicate-pr.ts -f pr_info.txt "Check the attached file for PR details and search for duplicates") - - gh pr comment "$PR_NUMBER" --body "_The following comment was made by an LLM, it may be inaccurate:_ - - $COMMENT" diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml deleted file mode 100644 index 29cc9895393b..000000000000 --- a/.github/workflows/generate.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: generate - -on: - push: - branches: - - dev - workflow_dispatch: - -jobs: - generate: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - ref: ${{ github.event.pull_request.head.ref || github.ref_name }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Generate - run: ./script/generate.ts - - - name: Commit and push - run: | - if [ -z "$(git status --porcelain)" ]; then - echo "No changes to commit" - exit 0 - fi - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add -A - git commit -m "chore: generate" - git push origin HEAD:${{ github.ref_name }} --no-verify - # if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then - # echo "" - # echo "============================================" - # echo "Failed to push generated code." - # echo "Please run locally and push:" - # echo "" - # echo " ./script/generate.ts" - # echo " git add -A && git commit -m \"chore: generate\" && git push" - # echo "" - # echo "============================================" - # exit 1 - # fi diff --git a/.github/workflows/nix-desktop.yml b/.github/workflows/nix-desktop.yml deleted file mode 100644 index 3d7c48031332..000000000000 --- a/.github/workflows/nix-desktop.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: nix desktop - -on: - push: - branches: [dev] - paths: - - "flake.nix" - - "flake.lock" - - "nix/**" - - "packages/app/**" - - "packages/desktop/**" - - ".github/workflows/nix-desktop.yml" - pull_request: - paths: - - "flake.nix" - - "flake.lock" - - "nix/**" - - "packages/app/**" - - "packages/desktop/**" - - ".github/workflows/nix-desktop.yml" - workflow_dispatch: - -jobs: - build-desktop: - strategy: - fail-fast: false - matrix: - os: - - blacksmith-4vcpu-ubuntu-2404 - - blacksmith-4vcpu-ubuntu-2404-arm - - macos-15-intel - - macos-latest - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Setup Nix - uses: nixbuild/nix-quick-install-action@v34 - - - name: Build desktop via flake - run: | - set -euo pipefail - nix --version - nix build .#desktop -L diff --git a/.github/workflows/notify-discord.yml b/.github/workflows/notify-discord.yml deleted file mode 100644 index 62577ecf00e9..000000000000 --- a/.github/workflows/notify-discord.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: discord - -on: - release: - types: [released] # fires when a draft release is published - -jobs: - notify: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - name: Send nicely-formatted embed to Discord - uses: SethCohen/github-releases-to-discord@v1 - with: - webhook_url: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml deleted file mode 100644 index 76e75fcaefb9..000000000000 --- a/.github/workflows/opencode.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: opencode - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - -jobs: - opencode: - if: | - contains(github.event.comment.body, ' /oc') || - startsWith(github.event.comment.body, '/oc') || - contains(github.event.comment.body, ' /opencode') || - startsWith(github.event.comment.body, '/opencode') - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - id-token: write - contents: read - pull-requests: read - issues: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - uses: ./.github/actions/setup-bun - - - name: Run opencode - uses: anomalyco/opencode/github@latest - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - OPENCODE_PERMISSION: '{"bash": "deny"}' - with: - model: opencode/claude-opus-4-5 diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml deleted file mode 100644 index c1cf1756787c..000000000000 --- a/.github/workflows/pr-standards.yml +++ /dev/null @@ -1,139 +0,0 @@ -name: PR Standards - -on: - pull_request_target: - types: [opened, edited, synchronize] - -jobs: - check-standards: - if: | - github.event.pull_request.user.login != 'actions-user' && - github.event.pull_request.user.login != 'opencode' && - github.event.pull_request.user.login != 'rekram1-node' && - github.event.pull_request.user.login != 'thdxr' && - github.event.pull_request.user.login != 'kommander' && - github.event.pull_request.user.login != 'jayair' && - github.event.pull_request.user.login != 'fwang' && - github.event.pull_request.user.login != 'adamdotdevin' && - github.event.pull_request.user.login != 'iamdavidhill' && - github.event.pull_request.user.login != 'opencode-agent[bot]' - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Check PR standards - uses: actions/github-script@v7 - with: - script: | - const pr = context.payload.pull_request; - const title = pr.title; - - async function addLabel(label) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: [label] - }); - } - - async function removeLabel(label) { - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - name: label - }); - } catch (e) { - // Label wasn't present, ignore - } - } - - async function comment(marker, body) { - const markerText = ``; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number - }); - - const existing = comments.find(c => c.body.includes(markerText)); - if (existing) return; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: markerText + '\n' + body - }); - } - - // Step 1: Check title format - // Matches: feat:, feat(scope):, feat (scope):, etc. - const titlePattern = /^(feat|fix|docs|chore|refactor|test)\s*(\([a-zA-Z0-9-]+\))?\s*:/; - const hasValidTitle = titlePattern.test(title); - - if (!hasValidTitle) { - await addLabel('needs:title'); - await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format. - - Please update it to start with one of: - - \`feat:\` or \`feat(scope):\` new feature - - \`fix:\` or \`fix(scope):\` bug fix - - \`docs:\` or \`docs(scope):\` documentation changes - - \`chore:\` or \`chore(scope):\` maintenance tasks - - \`refactor:\` or \`refactor(scope):\` code refactoring - - \`test:\` or \`test(scope):\` adding or updating tests - - Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`). - - See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`); - return; - } - - await removeLabel('needs:title'); - - // Step 2: Check for linked issue (skip for docs/refactor PRs) - const skipIssueCheck = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title); - if (skipIssueCheck) { - await removeLabel('needs:issue'); - console.log('Skipping issue check for docs/refactor PR'); - return; - } - const query = ` - query($owner: String!, $repo: String!, $number: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $number) { - closingIssuesReferences(first: 1) { - totalCount - } - } - } - } - `; - - const result = await github.graphql(query, { - owner: context.repo.owner, - repo: context.repo.repo, - number: pr.number - }); - - const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount; - - if (linkedIssues === 0) { - await addLabel('needs:issue'); - await comment('issue', `Thanks for your contribution! - - This PR doesn't have a linked issue. All PRs must reference an existing issue. - - Please: - 1. Open an issue describing the bug/feature (if one doesn't exist) - 2. Add \`Fixes #\` or \`Closes #\` to this PR description - - See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`); - return; - } - - await removeLabel('needs:issue'); - console.log('PR meets all standards'); diff --git a/.github/workflows/publish-github-action.yml b/.github/workflows/publish-github-action.yml deleted file mode 100644 index d2789373a34a..000000000000 --- a/.github/workflows/publish-github-action.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: publish-github-action - -on: - workflow_dispatch: - push: - tags: - - "github-v*.*.*" - - "!github-v1" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - -jobs: - publish: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - run: git fetch --force --tags - - - name: Publish - run: | - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - ./script/publish - working-directory: ./github diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml deleted file mode 100644 index f49a10578072..000000000000 --- a/.github/workflows/publish-vscode.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: publish-vscode - -on: - workflow_dispatch: - push: - tags: - - "vscode-v*.*.*" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - -jobs: - publish: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: ./.github/actions/setup-bun - - - run: git fetch --force --tags - - run: bun install -g @vscode/vsce - - - name: Install extension dependencies - run: bun install - working-directory: ./sdks/vscode - - - name: Publish - run: | - ./script/publish - working-directory: ./sdks/vscode - env: - VSCE_PAT: ${{ secrets.VSCE_PAT }} - OPENVSX_TOKEN: ${{ secrets.OPENVSX_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 8d7a823b1440..000000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,237 +0,0 @@ -name: publish -run-name: "${{ format('release {0}', inputs.bump) }}" - -on: - push: - branches: - - dev - - snapshot-* - workflow_dispatch: - inputs: - bump: - description: "Bump major, minor, or patch" - required: false - type: choice - options: - - major - - minor - - patch - version: - description: "Override version (optional)" - required: false - type: string - -concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }} - -permissions: - id-token: write - contents: write - packages: write - -jobs: - publish: - runs-on: blacksmith-4vcpu-ubuntu-2404 - if: github.repository == 'anomalyco/opencode' - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - run: git fetch --force --tags - - - uses: ./.github/actions/setup-bun - - - name: Install OpenCode - if: inputs.bump || inputs.version - run: bun i -g opencode-ai@1.0.169 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - uses: actions/setup-node@v4 - with: - node-version: "24" - registry-url: "https://registry.npmjs.org" - - - name: Setup Git Identity - run: | - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - git remote set-url origin https://x-access-token:${{ secrets.SST_GITHUB_TOKEN }}@github.com/${{ github.repository }} - - - name: Publish - id: publish - run: ./script/publish-start.ts - env: - OPENCODE_BUMP: ${{ inputs.bump }} - OPENCODE_VERSION: ${{ inputs.version }} - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - AUR_KEY: ${{ secrets.AUR_KEY }} - GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} - NPM_CONFIG_PROVENANCE: false - - - uses: actions/upload-artifact@v4 - with: - name: opencode-cli - path: packages/opencode/dist - - outputs: - release: ${{ steps.publish.outputs.release }} - tag: ${{ steps.publish.outputs.tag }} - version: ${{ steps.publish.outputs.version }} - - publish-tauri: - needs: publish - continue-on-error: false - strategy: - fail-fast: false - matrix: - settings: - - host: macos-latest - target: x86_64-apple-darwin - - host: macos-latest - target: aarch64-apple-darwin - - host: blacksmith-4vcpu-windows-2025 - target: x86_64-pc-windows-msvc - - host: blacksmith-4vcpu-ubuntu-2404 - target: x86_64-unknown-linux-gnu - - host: blacksmith-4vcpu-ubuntu-2404-arm - target: aarch64-unknown-linux-gnu - runs-on: ${{ matrix.settings.host }} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ needs.publish.outputs.tag }} - - - uses: apple-actions/import-codesign-certs@v2 - if: ${{ runner.os == 'macOS' }} - with: - keychain: build - p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} - p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - - - name: Verify Certificate - if: ${{ runner.os == 'macOS' }} - run: | - CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") - CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') - echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV - echo "Certificate imported." - - - name: Setup Apple API Key - if: ${{ runner.os == 'macOS' }} - run: | - echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8 - - - run: git fetch --force --tags - - - uses: ./.github/actions/setup-bun - - - name: install dependencies (ubuntu only) - if: contains(matrix.settings.host, 'ubuntu') - run: | - sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - - - name: install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.settings.target }} - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: packages/desktop/src-tauri - shared-key: ${{ matrix.settings.target }} - - - name: Prepare - run: | - cd packages/desktop - bun ./scripts/prepare.ts - env: - OPENCODE_VERSION: ${{ needs.publish.outputs.version }} - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} - GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} - AUR_KEY: ${{ secrets.AUR_KEY }} - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - RUST_TARGET: ${{ matrix.settings.target }} - GH_TOKEN: ${{ github.token }} - GITHUB_RUN_ID: ${{ github.run_id }} - - # Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released - - name: Install tauri-cli from portable appimage branch - if: contains(matrix.settings.host, 'ubuntu') - run: | - cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage --force - echo "Installed tauri-cli version:" - cargo tauri --version - - - name: Build and upload artifacts - uses: Wandalen/wretry.action@v3 - timeout-minutes: 60 - with: - attempt_limit: 3 - attempt_delay: 10000 - action: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a - with: | - projectPath: packages/desktop - uploadWorkflowArtifacts: true - tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }} - args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose - updaterJsonPreferNsis: true - releaseId: ${{ needs.publish.outputs.release }} - tagName: ${{ needs.publish.outputs.tag }} - releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext] - releaseDraft: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8 - - publish-release: - needs: - - publish - - publish-tauri - if: needs.publish.outputs.tag - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ needs.publish.outputs.tag }} - - - uses: ./.github/actions/setup-bun - - - name: Setup SSH for AUR - run: | - sudo apt-get update - sudo apt-get install -y pacman-package-manager - mkdir -p ~/.ssh - echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true - - - run: ./script/publish-complete.ts - env: - OPENCODE_VERSION: ${{ needs.publish.outputs.version }} - AUR_KEY: ${{ secrets.AUR_KEY }} - GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} diff --git a/.github/workflows/release-github-action.yml b/.github/workflows/release-github-action.yml deleted file mode 100644 index 3f5caa55c8dc..000000000000 --- a/.github/workflows/release-github-action.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: release-github-action - -on: - push: - branches: - - dev - paths: - - "github/**" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - -jobs: - release: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - run: git fetch --force --tags - - - name: Release - run: | - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - ./github/script/release diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml deleted file mode 100644 index 93b01bafa2b3..000000000000 --- a/.github/workflows/review.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Guidelines Check - -on: - issue_comment: - types: [created] - -jobs: - check-guidelines: - if: | - github.event.issue.pull_request && - startsWith(github.event.comment.body, '/review') && - contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association) - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - pull-requests: write - steps: - - name: Get PR number - id: pr-number - run: | - if [ "${{ github.event_name }}" = "pull_request_target" ]; then - echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT - else - echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT - fi - - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Get PR details - id: pr-details - run: | - gh api /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }} > pr_data.json - echo "title=$(jq -r .title pr_data.json)" >> $GITHUB_OUTPUT - echo "sha=$(jq -r .head.sha pr_data.json)" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check PR guidelines compliance - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }' - PR_TITLE: ${{ steps.pr-details.outputs.title }} - run: | - PR_BODY=$(jq -r .body pr_data.json) - opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}' - - - ${{ steps.pr-number.outputs.number }} - - - - $PR_BODY - - - Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do - - When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage. - When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simplest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts) - - Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block. - If you are writing suggested fixes, BE SURE THAT the change you are recommending is actually valid typescript, often I have seen missing closing "}" or other syntax errors. - Generally, write a comment instead of writing suggested change if you can help it. - - Command MUST be like this. - \`\`\` - gh api \ - --method POST \ - -H \"Accept: application/vnd.github+json\" \ - -H \"X-GitHub-Api-Version: 2022-11-28\" \ - /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }}/comments \ - -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT' - \`\`\` - - Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!." diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml deleted file mode 100644 index b5378d7d5270..000000000000 --- a/.github/workflows/stale-issues.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: "Auto-close stale issues" - -on: - schedule: - - cron: "30 1 * * *" # Daily at 1:30 AM - workflow_dispatch: - -env: - DAYS_BEFORE_STALE: 90 - DAYS_BEFORE_CLOSE: 7 - -jobs: - stale: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: actions/stale@v10 - with: - days-before-stale: ${{ env.DAYS_BEFORE_STALE }} - days-before-close: ${{ env.DAYS_BEFORE_CLOSE }} - stale-issue-label: "stale" - close-issue-message: | - [automated] Closing due to ${{ env.DAYS_BEFORE_STALE }}+ days of inactivity. - - Feel free to reopen if you still need this! - stale-issue-message: | - [automated] This issue has had no activity for ${{ env.DAYS_BEFORE_STALE }} days. - - It will be closed in ${{ env.DAYS_BEFORE_CLOSE }} days if there's no new activity. - remove-stale-when-updated: true - exempt-issue-labels: "pinned,security,feature-request,on-hold" - start-date: "2025-12-27" diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml deleted file mode 100644 index 824733901d6b..000000000000 --- a/.github/workflows/stats.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: stats - -on: - schedule: - - cron: "0 12 * * *" # Run daily at 12:00 UTC - workflow_dispatch: # Allow manual trigger - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - stats: - if: github.repository == 'anomalyco/opencode' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Run stats script - run: bun script/stats.ts - - - name: Commit stats - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add STATS.md - git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)" - git push - env: - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} diff --git a/.github/workflows/sync-zed-extension.yml b/.github/workflows/sync-zed-extension.yml deleted file mode 100644 index f14487cde974..000000000000 --- a/.github/workflows/sync-zed-extension.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: "sync-zed-extension" - -on: - workflow_dispatch: - release: - types: [published] - -jobs: - zed: - name: Release Zed Extension - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: ./.github/actions/setup-bun - - - name: Get version tag - id: get_tag - run: | - if [ "${{ github.event_name }}" = "release" ]; then - TAG="${{ github.event.release.tag_name }}" - else - TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1) - fi - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "Using tag: ${TAG}" - - - name: Sync Zed extension - run: | - ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }} - env: - ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }} - ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 7c8c20a6252d..000000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,133 +0,0 @@ -name: test - -on: - push: - branches: - - dev - pull_request: - workflow_dispatch: -jobs: - test: - name: test (${{ matrix.settings.name }}) - strategy: - fail-fast: false - matrix: - settings: - - name: linux - host: blacksmith-4vcpu-ubuntu-2404 - playwright: bunx playwright install --with-deps - workdir: . - command: | - git config --global user.email "bot@opencode.ai" - git config --global user.name "opencode" - bun turbo typecheck - bun turbo test - - name: windows - host: windows-latest - playwright: bunx playwright install - workdir: packages/app - command: bun test:e2e - runs-on: ${{ matrix.settings.host }} - defaults: - run: - shell: bash - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Install Playwright browsers - working-directory: packages/app - run: ${{ matrix.settings.playwright }} - - - name: Set OS-specific paths - run: | - if [ "${{ runner.os }}" = "Windows" ]; then - printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}\\opencode-e2e" >> "$GITHUB_ENV" - printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}\\opencode-e2e\\home" >> "$GITHUB_ENV" - printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}\\opencode-e2e\\share" >> "$GITHUB_ENV" - printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}\\opencode-e2e\\cache" >> "$GITHUB_ENV" - printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}\\opencode-e2e\\config" >> "$GITHUB_ENV" - printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}\\opencode-e2e\\state" >> "$GITHUB_ENV" - printf '%s\n' "MODELS_DEV_API_JSON=${{ github.workspace }}\\packages\\opencode\\test\\tool\\fixtures\\models-api.json" >> "$GITHUB_ENV" - else - printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}/opencode-e2e" >> "$GITHUB_ENV" - printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}/opencode-e2e/home" >> "$GITHUB_ENV" - printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}/opencode-e2e/share" >> "$GITHUB_ENV" - printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}/opencode-e2e/cache" >> "$GITHUB_ENV" - printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}/opencode-e2e/config" >> "$GITHUB_ENV" - printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}/opencode-e2e/state" >> "$GITHUB_ENV" - printf '%s\n' "MODELS_DEV_API_JSON=${{ github.workspace }}/packages/opencode/test/tool/fixtures/models-api.json" >> "$GITHUB_ENV" - fi - - - name: Seed opencode data - working-directory: packages/opencode - run: bun script/seed-e2e.ts - env: - MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }} - OPENCODE_DISABLE_MODELS_FETCH: "true" - OPENCODE_DISABLE_SHARE: "true" - OPENCODE_DISABLE_LSP_DOWNLOAD: "true" - OPENCODE_DISABLE_DEFAULT_PLUGINS: "true" - OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true" - OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }} - XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }} - XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }} - XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }} - XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }} - OPENCODE_E2E_PROJECT_DIR: ${{ github.workspace }} - OPENCODE_E2E_SESSION_TITLE: "E2E Session" - OPENCODE_E2E_MESSAGE: "Seeded for UI e2e" - OPENCODE_E2E_MODEL: "opencode/gpt-5-nano" - - - name: Run opencode server - working-directory: packages/opencode - run: bun dev -- --print-logs --log-level WARN serve --port 4096 --hostname 0.0.0.0 & - env: - MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }} - OPENCODE_DISABLE_MODELS_FETCH: "true" - OPENCODE_DISABLE_SHARE: "true" - OPENCODE_DISABLE_LSP_DOWNLOAD: "true" - OPENCODE_DISABLE_DEFAULT_PLUGINS: "true" - OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true" - OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }} - XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }} - XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }} - XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }} - XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }} - OPENCODE_CLIENT: "app" - - - name: Wait for opencode server - run: | - for i in {1..60}; do - curl -fsS "http://localhost:4096/global/health" > /dev/null && exit 0 - sleep 1 - done - exit 1 - - - name: run - working-directory: ${{ matrix.settings.workdir }} - run: ${{ matrix.settings.command }} - env: - CI: true - MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }} - OPENCODE_DISABLE_MODELS_FETCH: "true" - OPENCODE_DISABLE_SHARE: "true" - OPENCODE_DISABLE_LSP_DOWNLOAD: "true" - OPENCODE_DISABLE_DEFAULT_PLUGINS: "true" - OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true" - OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }} - XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }} - XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }} - XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }} - XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }} - PLAYWRIGHT_SERVER_HOST: "localhost" - PLAYWRIGHT_SERVER_PORT: "4096" - VITE_OPENCODE_SERVER_HOST: "localhost" - VITE_OPENCODE_SERVER_PORT: "4096" - OPENCODE_CLIENT: "app" - timeout-minutes: 30 diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml deleted file mode 100644 index 6e1509572919..000000000000 --- a/.github/workflows/triage.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Issue Triage - -on: - issues: - types: [opened] - -jobs: - triage: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Triage issue - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - ISSUE_TITLE: ${{ github.event.issue.title }} - ISSUE_BODY: ${{ github.event.issue.body }} - run: | - opencode run --agent triage "The following issue was just opened, triage it: - - Title: $ISSUE_TITLE - - $ISSUE_BODY" diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml deleted file mode 100644 index 011e23f5f6fb..000000000000 --- a/.github/workflows/typecheck.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: typecheck - -on: - pull_request: - branches: [dev] - workflow_dispatch: - -jobs: - typecheck: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Run typecheck - run: bun typecheck diff --git a/.github/workflows/update-nix-hashes.yml b/.github/workflows/update-nix-hashes.yml deleted file mode 100644 index 7175f4fbdd61..000000000000 --- a/.github/workflows/update-nix-hashes.yml +++ /dev/null @@ -1,138 +0,0 @@ -name: Update Nix Hashes - -permissions: - contents: write - -on: - workflow_dispatch: - push: - paths: - - "bun.lock" - - "package.json" - - "packages/*/package.json" - - "flake.lock" - - ".github/workflows/update-nix-hashes.yml" - pull_request: - paths: - - "bun.lock" - - "package.json" - - "packages/*/package.json" - - "flake.lock" - - ".github/workflows/update-nix-hashes.yml" - -jobs: - update-node-modules-hashes: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: blacksmith-4vcpu-ubuntu-2404 - env: - TITLE: node_modules hashes - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - ref: ${{ github.head_ref || github.ref_name }} - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - - - name: Setup Nix - uses: nixbuild/nix-quick-install-action@v34 - - - name: Configure git - run: | - git config --global user.email "action@github.com" - git config --global user.name "Github Action" - - - name: Pull latest changes - env: - TARGET_BRANCH: ${{ github.head_ref || github.ref_name }} - run: | - BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}" - git pull --rebase --autostash origin "$BRANCH" - - - name: Compute all node_modules hashes - run: | - set -euo pipefail - - HASH_FILE="nix/hashes.json" - SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin" - - if [ ! -f "$HASH_FILE" ]; then - mkdir -p "$(dirname "$HASH_FILE")" - echo '{"nodeModules":{}}' > "$HASH_FILE" - fi - - for SYSTEM in $SYSTEMS; do - echo "Computing hash for ${SYSTEM}..." - BUILD_LOG=$(mktemp) - trap 'rm -f "$BUILD_LOG"' EXIT - - # The updater derivations use fakeHash, so they will fail and reveal the correct hash - UPDATER_ATTR=".#packages.x86_64-linux.${SYSTEM}_node_modules" - - nix build "$UPDATER_ATTR" --no-link 2>&1 | tee "$BUILD_LOG" || true - - CORRECT_HASH="$(grep -E 'got:\s+sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | awk '{print $2}' | head -n1 || true)" - - if [ -z "$CORRECT_HASH" ]; then - CORRECT_HASH="$(grep -A2 'hash mismatch' "$BUILD_LOG" | grep 'got:' | awk '{print $2}' | sed 's/sha256:/sha256-/' || true)" - fi - - if [ -z "$CORRECT_HASH" ]; then - echo "Failed to determine correct node_modules hash for ${SYSTEM}." - cat "$BUILD_LOG" - exit 1 - fi - - echo " ${SYSTEM}: ${CORRECT_HASH}" - jq --arg sys "$SYSTEM" --arg h "$CORRECT_HASH" \ - '.nodeModules[$sys] = $h' "$HASH_FILE" > "${HASH_FILE}.tmp" - mv "${HASH_FILE}.tmp" "$HASH_FILE" - done - - echo "All hashes computed:" - cat "$HASH_FILE" - - - name: Commit ${{ env.TITLE }} changes - env: - TARGET_BRANCH: ${{ github.head_ref || github.ref_name }} - run: | - set -euo pipefail - - HASH_FILE="nix/hashes.json" - echo "Checking for changes..." - - summarize() { - local status="$1" - { - echo "### Nix $TITLE" - echo "" - echo "- ref: ${GITHUB_REF_NAME}" - echo "- status: ${status}" - } >> "$GITHUB_STEP_SUMMARY" - if [ -n "${GITHUB_SERVER_URL:-}" ] && [ -n "${GITHUB_REPOSITORY:-}" ] && [ -n "${GITHUB_RUN_ID:-}" ]; then - echo "- run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" >> "$GITHUB_STEP_SUMMARY" - fi - echo "" >> "$GITHUB_STEP_SUMMARY" - } - - FILES=("$HASH_FILE") - STATUS="$(git status --short -- "${FILES[@]}" || true)" - if [ -z "$STATUS" ]; then - echo "No changes detected." - summarize "no changes" - exit 0 - fi - - echo "Changes detected:" - echo "$STATUS" - git add "${FILES[@]}" - git commit -m "chore: update nix node_modules hashes" - - BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}" - git pull --rebase --autostash origin "$BRANCH" - git push origin HEAD:"$BRANCH" - echo "Changes pushed successfully" - - summarize "committed $(git rev-parse --short HEAD)"