fix(agent): retire gemini cli provider, add antigravity and grok build
This commit is contained in:
parent
37f80d8e4a
commit
124db9d144
|
|
@ -64,7 +64,7 @@ Passt sich automatisch an die Fähigkeiten jedes Modells an. Claude erhält voll
|
|||
|
||||
### 🔌 MCP-Server
|
||||
|
||||
Ein-Klick-Installation in Claude Code, Codex, Gemini, OpenCode, Kiro oder Copilot CLIs. Designen Sie aus Ihrem Terminal — `.op`-Dateien über jeden MCP-kompatiblen Agenten lesen, erstellen und bearbeiten.
|
||||
Ein-Klick-Installation in Claude Code, Codex, OpenCode, Kiro oder Copilot CLIs. Designen Sie aus Ihrem Terminal — `.op`-Dateien über jeden MCP-kompatiblen Agenten lesen, erstellen und bearbeiten.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ Getaggte Rust-Releases veröffentlichen ein einzelnes Web-Host-Image. Die frühe
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust-Web-Host, wasm bundle und CanvasKit-Assets |
|
||||
|
||||
Die Web-UI zeigt nur integrierte Agent-Profile; Claude/Codex/OpenCode/Copilot/Gemini-CLI-Tools sind nicht in Docker-Images enthalten.
|
||||
Die Web-UI zeigt nur integrierte Agent-Profile; Claude/Codex/OpenCode/Copilot-CLI-Tools sind nicht in Docker-Images enthalten.
|
||||
|
||||
**Ausführen:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | In den Agenteneinstellungen verbinden (`Cmd+,`) |
|
||||
| **OpenCode** | In den Agenteneinstellungen verbinden (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` dann in den Agenteneinstellungen verbinden (`Cmd+,`) |
|
||||
| **Gemini CLI** | In den Agenteneinstellungen verbinden (`Cmd+,`) |
|
||||
|
||||
**Modell-Fähigkeitsprofile** — passt Prompts, Thinking-Modus und Timeouts automatisch pro Modellstufe an. Modelle der Vollstufe (Claude) erhalten vollständige Prompts; Standardstufe (GPT-4o, Gemini, DeepSeek) deaktiviert Thinking; Basisstufe (MiniMax, Qwen, Llama, Mistral) erhält vereinfachte verschachtelte JSON-Prompts für maximale Zuverlässigkeit.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP-Server**
|
||||
|
||||
- Eingebauter MCP-Server (`op-mcp`-Crate) — Ein-Klick-Installation in Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
|
||||
- Eingebauter MCP-Server (`op-mcp`-Crate) — Ein-Klick-Installation in Claude Code / Codex / OpenCode / Kiro / Copilot CLIs
|
||||
- Kein Node.js erforderlich — stdio-Transport über die Desktop-Binärdatei (`--mcp <path>`), plus ein Live-HTTP-Endpunkt (`127.0.0.1:<port>/mcp`) der laufenden App
|
||||
- Design-Automatisierung vom Terminal aus: `.op`-Dateien über jeden MCP-kompatiblen Agenten lesen, erstellen und bearbeiten
|
||||
- **Mehrstufiger Design-Workflow** — `design_skeleton` → `design_content` → `design_refine` für hochwertigere mehrteilige Designs
|
||||
|
|
@ -229,7 +228,7 @@ Unterstützt Inline-Strings, `@filepath` und stdin (`-`). Funktioniert mit Deskt
|
|||
- Mehrstufiger Workflow — `design_skeleton` → `design_content` → `design_refine` mit fokussierten Prompts pro Phase
|
||||
- Style Guides — 50+ eingebaute Stile (glassmorphism, brutalist, retro usw.) mit tag-basiertem Fuzzy-Matching, eingebunden in Planung und Generierung
|
||||
- Multi-Modell-Fähigkeitsprofile — passt Denkmodus, Aufwand und Promptform automatisch an die Modellstufe an
|
||||
- Integrierte Agent-Laufzeit (Rust) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini-Anbieter
|
||||
- Integrierte Agent-Laufzeit (Rust) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot und Google Gemini API
|
||||
- Anthropic-Format-Passthrough für chinesische LLM-Anbieter — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Git-Integration**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Se adapta automáticamente a las capacidades de cada modelo. Claude recibe promp
|
|||
|
||||
### 🔌 Servidor MCP
|
||||
|
||||
Instalación con un clic en Claude Code, Codex, Gemini, OpenCode, Kiro o Copilot CLIs. Diseña desde tu terminal — lee, crea y modifica archivos `.op` a través de cualquier agente compatible con MCP.
|
||||
Instalación con un clic en Claude Code, Codex, OpenCode, Kiro o Copilot CLIs. Diseña desde tu terminal — lee, crea y modifica archivos `.op` a través de cualquier agente compatible con MCP.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ Las releases Rust etiquetadas publican una única imagen de web host. Las imáge
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle y recursos de CanvasKit |
|
||||
|
||||
La UI web expone solo perfiles de agente integrados; las herramientas Claude/Codex/OpenCode/Copilot/Gemini CLI no se incluyen en las imágenes Docker.
|
||||
La UI web expone solo perfiles de agente integrados; las herramientas Claude/Codex/OpenCode/Copilot CLI no se incluyen en las imágenes Docker.
|
||||
|
||||
**Ejecutar:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | Conectar en Configuración de Agente (`Cmd+,`) |
|
||||
| **OpenCode** | Conectar en Configuración de Agente (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` y luego conectar en Configuración de Agente (`Cmd+,`) |
|
||||
| **Gemini CLI** | Conectar en Configuración de Agente (`Cmd+,`) |
|
||||
|
||||
**Perfiles de Capacidad de Modelos** — adapta automáticamente los prompts, el modo de pensamiento y los tiempos de espera según el nivel del modelo. Los modelos de nivel completo (Claude) reciben prompts completos; los de nivel estándar (GPT-4o, Gemini, DeepSeek) desactivan el pensamiento; los de nivel básico (MiniMax, Qwen, Llama, Mistral) reciben prompts simplificados de JSON anidado para máxima fiabilidad.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**Servidor MCP**
|
||||
|
||||
- Servidor MCP integrado (crate `op-mcp`) — instalación con un clic en Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
|
||||
- Servidor MCP integrado (crate `op-mcp`) — instalación con un clic en Claude Code / Codex / OpenCode / Kiro / Copilot CLIs
|
||||
- No requiere Node.js — transporte stdio a través del binario de escritorio (`--mcp <path>`), además de un endpoint HTTP en vivo (`127.0.0.1:<port>/mcp`) desde la app en ejecución
|
||||
- Automatización de diseño desde la terminal: leer, crear y modificar archivos `.op` a través de cualquier agente compatible con MCP
|
||||
- **Flujo de diseño por capas** — `design_skeleton` → `design_content` → `design_refine` para diseños multisección de mayor fidelidad
|
||||
|
|
@ -229,7 +228,7 @@ Admite cadenas inline, `@filepath` y stdin (`-`). Funciona con la app de escrito
|
|||
- Flujo de trabajo por capas — `design_skeleton` → `design_content` → `design_refine` con prompts enfocados por fase
|
||||
- Guías de estilo — más de 50 estilos integrados (glassmorphism, brutalist, retro, etc.) con coincidencia difusa basada en etiquetas, integrada en planificación y generación
|
||||
- Perfiles multi-modelo — adapta automáticamente el modo de pensamiento, el esfuerzo y la forma del prompt por nivel de modelo
|
||||
- Runtime de agente integrado (Rust) + proveedores Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini
|
||||
- Runtime de agente integrado (Rust) + proveedores Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot y Google Gemini API
|
||||
- Passthrough en formato Anthropic para proveedores de LLM chinos — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Integración con Git**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ S'adapte automatiquement aux capacités de chaque modèle. Claude obtient des pr
|
|||
|
||||
### 🔌 Serveur MCP
|
||||
|
||||
Installation en un clic dans les CLI Claude Code, Codex, Gemini, OpenCode, Kiro ou Copilot. Designez depuis votre terminal — lisez, créez et modifiez des fichiers `.op` via tout agent compatible MCP.
|
||||
Installation en un clic dans les CLI Claude Code, Codex, OpenCode, Kiro ou Copilot. Designez depuis votre terminal — lisez, créez et modifiez des fichiers `.op` via tout agent compatible MCP.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ Les releases Rust taguées publient une seule image web host. Les anciennes imag
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle et ressources CanvasKit |
|
||||
|
||||
L'interface web expose uniquement les profils d'agents intégrés ; les outils Claude/Codex/OpenCode/Copilot/Gemini CLI ne sont pas inclus dans les images Docker.
|
||||
L'interface web expose uniquement les profils d'agents intégrés ; les outils Claude/Codex/OpenCode/Copilot CLI ne sont pas inclus dans les images Docker.
|
||||
|
||||
**Exécuter :**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | Connecter dans les Paramètres de l'agent (`Cmd+,`) |
|
||||
| **OpenCode** | Connecter dans les Paramètres de l'agent (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` puis connecter dans les Paramètres de l'agent (`Cmd+,`) |
|
||||
| **Gemini CLI** | Connecter dans les Paramètres de l'agent (`Cmd+,`) |
|
||||
|
||||
**Profils de capacités des modèles** — adapte automatiquement les prompts, le mode de réflexion et les délais d'attente par niveau de modèle. Les modèles de niveau complet (Claude) reçoivent des prompts complets ; le niveau standard (GPT-4o, Gemini, DeepSeek) désactive la réflexion ; le niveau basique (MiniMax, Qwen, Llama, Mistral) reçoit des prompts JSON imbriqués simplifiés pour une fiabilité maximale.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**Serveur MCP**
|
||||
|
||||
- Serveur MCP intégré (crate `op-mcp`) — installation en un clic dans les CLI Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot
|
||||
- Serveur MCP intégré (crate `op-mcp`) — installation en un clic dans les CLI Claude Code / Codex / OpenCode / Kiro / Copilot
|
||||
- Aucun besoin de Node.js — transport stdio via le binaire de bureau (`--mcp <path>`), plus un point de terminaison HTTP en direct (`127.0.0.1:<port>/mcp`) depuis l'application en cours d'exécution
|
||||
- Automatisation du design depuis le terminal : lire, créer et modifier des fichiers `.op` via tout agent compatible MCP
|
||||
- **Workflow de design en couches** — `design_skeleton` → `design_content` → `design_refine` pour des designs multi-sections de plus haute fidélité
|
||||
|
|
@ -229,7 +228,7 @@ Prend en charge les chaînes en ligne, `@filepath` et stdin (`-`). Fonctionne av
|
|||
- Workflow en couches — `design_skeleton` → `design_content` → `design_refine` avec des prompts ciblés par phase
|
||||
- Guides de style — plus de 50 styles intégrés (glassmorphism, brutalist, retro, etc.) avec appariement flou basé sur les tags, intégrés dans la planification et la génération
|
||||
- Profils de capacités multi-modèles — adapte automatiquement le mode de réflexion, l'effort et la forme du prompt selon le niveau du modèle
|
||||
- Runtime d'agent intégré (Rust) + fournisseurs Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini
|
||||
- Runtime d'agent intégré (Rust) + fournisseurs Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot et Google Gemini API
|
||||
- Passthrough au format Anthropic pour les fournisseurs LLM chinois — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Intégration Git**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
### 🔌 MCP सर्वर
|
||||
|
||||
Claude Code, Codex, Gemini, OpenCode, Kiro, या Copilot CLIs में वन-क्लिक इंस्टॉल। अपने टर्मिनल से डिज़ाइन करें — किसी भी MCP-संगत एजेंट के ज़रिए `.op` फ़ाइलें पढ़ें, बनाएँ और संशोधित करें।
|
||||
Claude Code, Codex, OpenCode, Kiro, या Copilot CLIs में वन-क्लिक इंस्टॉल। अपने टर्मिनल से डिज़ाइन करें — किसी भी MCP-संगत एजेंट के ज़रिए `.op` फ़ाइलें पढ़ें, बनाएँ और संशोधित करें।
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ cargo run -p op-host-desktop
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle और CanvasKit assets |
|
||||
|
||||
Web UI केवल built-in agent profiles दिखाता है; Claude/Codex/OpenCode/Copilot/Gemini CLI tools Docker images में शामिल नहीं हैं।
|
||||
Web UI केवल built-in agent profiles दिखाता है; Claude/Codex/OpenCode/Copilot CLI tools Docker images में शामिल नहीं हैं।
|
||||
|
||||
**चलाएँ:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | एजेंट सेटिंग्स में कनेक्ट करें (`Cmd+,`) |
|
||||
| **OpenCode** | एजेंट सेटिंग्स में कनेक्ट करें (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` फिर एजेंट सेटिंग्स में कनेक्ट करें (`Cmd+,`) |
|
||||
| **Gemini CLI** | एजेंट सेटिंग्स में कनेक्ट करें (`Cmd+,`) |
|
||||
|
||||
**मॉडल क्षमता प्रोफ़ाइल** — प्रत्येक मॉडल टियर के अनुसार प्रॉम्प्ट, थिंकिंग मोड और टाइमआउट को स्वचालित रूप से अनुकूलित करता है। फुल-टियर मॉडल (Claude) को पूर्ण प्रॉम्प्ट मिलते हैं; स्टैंडर्ड-टियर (GPT-4o, Gemini, DeepSeek) में थिंकिंग अक्षम होती है; बेसिक-टियर (MiniMax, Qwen, Llama, Mistral) को अधिकतम विश्वसनीयता के लिए सरलीकृत नेस्टेड-JSON प्रॉम्प्ट मिलते हैं।
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP सर्वर**
|
||||
|
||||
- बिल्ट-इन MCP सर्वर (`op-mcp` crate) — Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs में वन-क्लिक इंस्टॉल
|
||||
- बिल्ट-इन MCP सर्वर (`op-mcp` crate) — Claude Code / Codex / OpenCode / Kiro / Copilot CLIs में वन-क्लिक इंस्टॉल
|
||||
- Node.js की कोई आवश्यकता नहीं — डेस्कटॉप बाइनरी (`--mcp <path>`) के ज़रिए stdio ट्रांसपोर्ट, साथ ही चल रहे ऐप से एक लाइव HTTP एंडपॉइंट (`127.0.0.1:<port>/mcp`)
|
||||
- टर्मिनल से डिज़ाइन ऑटोमेशन: किसी भी MCP-संगत एजेंट के ज़रिए `.op` फ़ाइलें पढ़ें, बनाएँ और संपादित करें
|
||||
- **लेयर्ड डिज़ाइन वर्कफ़्लो** — उच्च-फ़िडेलिटी मल्टी-सेक्शन डिज़ाइन के लिए `design_skeleton` → `design_content` → `design_refine`
|
||||
|
|
@ -229,7 +228,7 @@ cat design.dsl | op design - # stdin से पाइप करें
|
|||
- लेयर्ड वर्कफ़्लो — `design_skeleton` → `design_content` → `design_refine`, प्रत्येक चरण के लिए केंद्रित प्रॉम्प्ट
|
||||
- स्टाइल गाइड — 50+ इन-बिल्ट स्टाइल (glassmorphism, brutalist, retro आदि), टैग-आधारित फ़ज़ी मैचिंग, प्लानिंग और जनरेशन में एकीकृत
|
||||
- मल्टी-मॉडल क्षमता प्रोफ़ाइल — मॉडल स्तर के अनुसार थिंकिंग मोड, प्रयास और प्रॉम्प्ट रूप को स्वचालित रूप से अनुकूलित करता है
|
||||
- बिल्ट-इन एजेंट रनटाइम (Rust) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini प्रदाता
|
||||
- बिल्ट-इन एजेंट रनटाइम (Rust) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot और Google Gemini API प्रदाता
|
||||
- चीनी LLM प्रदाताओं के लिए Anthropic फ़ॉर्मेट पासथ्रू — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Git इंटीग्रेशन**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Secara otomatis menyesuaikan dengan kemampuan setiap model. Claude mendapat prom
|
|||
|
||||
### 🔌 Server MCP
|
||||
|
||||
Instal satu klik ke Claude Code, Codex, Gemini, OpenCode, Kiro, atau Copilot CLI. Desain dari terminal Anda — baca, buat, dan modifikasi file `.op` melalui agen yang kompatibel dengan MCP.
|
||||
Instal satu klik ke Claude Code, Codex, OpenCode, Kiro, atau Copilot CLI. Desain dari terminal Anda — baca, buat, dan modifikasi file `.op` melalui agen yang kompatibel dengan MCP.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ Rilis Rust bertag menerbitkan satu image web host. Image TypeScript lama yang me
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle, dan aset CanvasKit |
|
||||
|
||||
Web UI hanya menampilkan built-in agent profiles; alat Claude/Codex/OpenCode/Copilot/Gemini CLI tidak dibundel ke dalam image Docker.
|
||||
Web UI hanya menampilkan built-in agent profiles; alat Claude/Codex/OpenCode/Copilot CLI tidak dibundel ke dalam image Docker.
|
||||
|
||||
**Jalankan:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | Hubungkan di Pengaturan Agen (`Cmd+,`) |
|
||||
| **OpenCode** | Hubungkan di Pengaturan Agen (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` lalu hubungkan di Pengaturan Agen (`Cmd+,`) |
|
||||
| **Gemini CLI** | Hubungkan di Pengaturan Agen (`Cmd+,`) |
|
||||
|
||||
**Profil Kemampuan Model** — secara otomatis menyesuaikan prompt, mode thinking, dan timeout per tingkatan model. Model tingkat penuh (Claude) mendapat prompt lengkap; tingkat standar (GPT-4o, Gemini, DeepSeek) menonaktifkan thinking; tingkat dasar (MiniMax, Qwen, Llama, Mistral) mendapat prompt JSON bertingkat yang disederhanakan untuk keandalan maksimum.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**Server MCP**
|
||||
|
||||
- Server MCP bawaan (crate `op-mcp`) — instal satu klik ke Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
|
||||
- Server MCP bawaan (crate `op-mcp`) — instal satu klik ke Claude Code / Codex / OpenCode / Kiro / Copilot CLI
|
||||
- Tidak memerlukan Node.js — transport stdio melalui binary desktop (`--mcp <path>`), ditambah endpoint HTTP langsung (`127.0.0.1:<port>/mcp`) dari aplikasi yang berjalan
|
||||
- Otomasi desain dari terminal: baca, buat, dan modifikasi file `.op` melalui agen yang kompatibel dengan MCP
|
||||
- **Alur kerja desain berlapis** — `design_skeleton` → `design_content` → `design_refine` untuk desain multi-bagian dengan fidelitas lebih tinggi
|
||||
|
|
@ -229,7 +228,7 @@ Mendukung string inline, `@filepath`, dan stdin (`-`). Bekerja dengan aplikasi d
|
|||
- Alur kerja berlapis — `design_skeleton` → `design_content` → `design_refine` dengan prompt yang terfokus per fase
|
||||
- Panduan Gaya — 50+ gaya bawaan (glassmorphism, brutalist, retro, dll.) dengan pencocokan fuzzy berbasis tag, terintegrasi ke perencanaan dan pembuatan
|
||||
- Profil kemampuan multi-model — secara otomatis menyesuaikan mode berpikir, upaya, dan bentuk prompt berdasarkan tingkat model
|
||||
- Runtime agen bawaan (Rust) + penyedia Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini
|
||||
- Runtime agen bawaan (Rust) + penyedia Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, dan Google Gemini API
|
||||
- Passthrough format Anthropic untuk penyedia LLM Tiongkok — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Integrasi Git**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
### 🔌 MCP サーバー
|
||||
|
||||
Claude Code、Codex、Gemini、OpenCode、Kiro、Copilot CLI にワンクリックでインストール。ターミナルからデザイン — MCP 対応エージェントを通じて `.op` ファイルの読み取り、作成、編集が可能。
|
||||
Claude Code、Codex、OpenCode、Kiro、Copilot CLI にワンクリックでインストール。ターミナルからデザイン — MCP 対応エージェントを通じて `.op` ファイルの読み取り、作成、編集が可能。
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ cargo run -p op-host-desktop
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host、wasm bundle、CanvasKit assets |
|
||||
|
||||
Web UI は built-in agent profiles のみを公開します。Claude/Codex/OpenCode/Copilot/Gemini CLI ツールは Docker イメージに同梱されません。
|
||||
Web UI は built-in agent profiles のみを公開します。Claude/Codex/OpenCode/Copilot CLI ツールは Docker イメージに同梱されません。
|
||||
|
||||
**実行:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | エージェント設定で接続(`Cmd+,`) |
|
||||
| **OpenCode** | エージェント設定で接続(`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` 後、エージェント設定で接続(`Cmd+,`) |
|
||||
| **Gemini CLI** | エージェント設定で接続(`Cmd+,`) |
|
||||
|
||||
**モデル能力プロファイル** — モデルの階層に応じてプロンプト、シンキングモード、タイムアウトを自動適応。フル階層モデル(Claude)には完全なプロンプト、標準階層(GPT-4o、Gemini、DeepSeek)ではシンキングを無効化、ベーシック階層(MiniMax、Qwen、Llama、Mistral)には最大限の信頼性のために簡略化されたネスト JSON プロンプトを使用。
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP サーバー**
|
||||
|
||||
- 内蔵 MCP サーバー(`op-mcp` crate) — Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI にワンクリックでインストール
|
||||
- 内蔵 MCP サーバー(`op-mcp` crate) — Claude Code / Codex / OpenCode / Kiro / Copilot CLI にワンクリックでインストール
|
||||
- Node.js は不要 — デスクトップバイナリ経由の stdio トランスポート(`--mcp <path>`)に加え、実行中のアプリからライブ HTTP エンドポイント(`127.0.0.1:<port>/mcp`)を提供
|
||||
- ターミナルからのデザイン自動化:MCP 対応エージェントを通じて `.op` ファイルの読み取り、作成、編集が可能
|
||||
- **レイヤードデザインワークフロー** — `design_skeleton` → `design_content` → `design_refine` による高忠実度マルチセクションデザイン
|
||||
|
|
@ -229,7 +228,7 @@ cat design.dsl | op design - # stdin からパイプ入力
|
|||
- レイヤードワークフロー — `design_skeleton` → `design_content` → `design_refine`、各フェーズごとに焦点を絞ったプロンプト
|
||||
- スタイルガイド — 50+ のビルトインスタイル(glassmorphism、brutalist、retro など)、タグベースのファジーマッチング対応、プランニングと生成に統合
|
||||
- マルチモデル能力プロファイル — モデル階層に応じてシンキングモード、エフォート、プロンプト形状を自動適応
|
||||
- ビルトインエージェントランタイム(Rust)+ Anthropic、Claude Agent SDK、OpenCode、Codex、Copilot、Gemini プロバイダー
|
||||
- ビルトインエージェントランタイム(Rust)+ Anthropic、Claude Agent SDK、OpenCode、Codex、Copilot、Google Gemini API プロバイダー
|
||||
- 中国系 LLM プロバイダー向け Anthropic フォーマットパススルー — Kimi、Zhipu、GLM、DouBao、Ark、Bailian/DashScope、ModelScope、Coding Plans
|
||||
|
||||
**Git 統合**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
### 🔌 MCP 서버
|
||||
|
||||
Claude Code, Codex, Gemini, OpenCode, Kiro 또는 Copilot CLI에 원클릭 설치. 터미널에서 디자인하세요 — MCP 호환 에이전트를 통해 `.op` 파일을 읽고, 생성하고, 편집할 수 있습니다.
|
||||
Claude Code, Codex, OpenCode, Kiro 또는 Copilot CLI에 원클릭 설치. 터미널에서 디자인하세요 — MCP 호환 에이전트를 통해 `.op` 파일을 읽고, 생성하고, 편집할 수 있습니다.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ cargo run -p op-host-desktop
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle, CanvasKit assets |
|
||||
|
||||
웹 UI는 내장 agent profiles만 노출합니다. Claude/Codex/OpenCode/Copilot/Gemini CLI 도구는 Docker 이미지에 포함되지 않습니다.
|
||||
웹 UI는 내장 agent profiles만 노출합니다. Claude/Codex/OpenCode/Copilot CLI 도구는 Docker 이미지에 포함되지 않습니다.
|
||||
|
||||
**실행:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | 에이전트 설정에서 연결 (`Cmd+,`) |
|
||||
| **OpenCode** | 에이전트 설정에서 연결 (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` 후 에이전트 설정에서 연결 (`Cmd+,`) |
|
||||
| **Gemini CLI** | 에이전트 설정에서 연결 (`Cmd+,`) |
|
||||
|
||||
**모델 역량 프로파일** — 모델 티어에 따라 프롬프트, 사고 모드, 타임아웃을 자동 조정합니다. 풀 티어 모델(Claude)은 완전한 프롬프트를 받고, 스탠다드 티어(GPT-4o, Gemini, DeepSeek)는 사고 모드를 비활성화하며, 베이직 티어(MiniMax, Qwen, Llama, Mistral)는 최대 안정성을 위해 단순화된 중첩 JSON 프롬프트를 받습니다.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP 서버**
|
||||
|
||||
- 내장 MCP 서버 (`op-mcp` 크레이트) — Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI에 원클릭 설치
|
||||
- 내장 MCP 서버 (`op-mcp` 크레이트) — Claude Code / Codex / OpenCode / Kiro / Copilot CLI에 원클릭 설치
|
||||
- Node.js 불필요 — 데스크톱 바이너리(`--mcp <path>`)를 통한 stdio 전송, 그리고 실행 중인 앱이 제공하는 실시간 HTTP 엔드포인트(`127.0.0.1:<port>/mcp`)
|
||||
- 터미널에서 디자인 자동화: MCP 호환 에이전트를 통해 `.op` 파일 읽기, 생성, 편집
|
||||
- **계층적 디자인 워크플로** — `design_skeleton` → `design_content` → `design_refine`으로 더 높은 충실도의 멀티 섹션 디자인
|
||||
|
|
@ -229,7 +228,7 @@ cat design.dsl | op design - # stdin에서 파이프 입력
|
|||
- 계층적 워크플로 — `design_skeleton` → `design_content` → `design_refine`, 각 단계에 집중된 프롬프트 사용
|
||||
- 스타일 가이드 — 50개 이상의 내장 스타일(glassmorphism, brutalist, retro 등), 태그 기반 퍼지 매칭 지원, 플래닝과 생성에 통합
|
||||
- 멀티 모델 역량 프로파일 — 모델 등급별로 싱킹 모드, 노력도, 프롬프트 형태를 자동 적응
|
||||
- 내장 에이전트 런타임(Rust) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini 제공자
|
||||
- 내장 에이전트 런타임(Rust) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Google Gemini API 제공자
|
||||
- 중국 LLM 제공자를 위한 Anthropic 형식 패스스루 — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Git 통합**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Automatically adapts to each model's capabilities. Claude gets full prompts with
|
|||
|
||||
### 🔌 MCP Server
|
||||
|
||||
One-click install into Claude Code, Codex, Gemini, OpenCode, Kiro, or Copilot CLIs. Design from your terminal — read, create, and modify `.op` files through any MCP-compatible agent.
|
||||
One-click install into Claude Code, Codex, OpenCode, Kiro, or Copilot CLIs. Design from your terminal — read, create, and modify `.op` files through any MCP-compatible agent.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -243,7 +243,7 @@ Tagged Rust releases publish a single web-host image. The retired TypeScript ima
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle, and CanvasKit assets |
|
||||
|
||||
The web UI exposes built-in agent profiles only; Claude/Codex/OpenCode/Copilot/Gemini CLI tooling is not bundled into Docker images.
|
||||
The web UI exposes built-in agent profiles only; Claude/Codex/OpenCode/Copilot CLI tooling is not bundled into Docker images.
|
||||
|
||||
**Run:**
|
||||
|
||||
|
|
@ -282,7 +282,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | Connect in Agent Settings (`Cmd+,`) |
|
||||
| **OpenCode** | Connect in Agent Settings (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` then connect in Agent Settings (`Cmd+,`) |
|
||||
| **Gemini CLI** | Connect in Agent Settings (`Cmd+,`) |
|
||||
|
||||
**Model Capability Profiles** — automatically adapts prompts, thinking mode, and timeouts per model tier. Full-tier models (Claude) get complete prompts; standard-tier (GPT-4o, Gemini, DeepSeek) disable thinking; basic-tier (MiniMax, Qwen, Llama, Mistral) get simplified nested-JSON prompts for maximum reliability.
|
||||
|
||||
|
|
@ -290,7 +289,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP Server**
|
||||
|
||||
- Built-in MCP server (`op-mcp` crate) — one-click install into Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
|
||||
- Built-in MCP server (`op-mcp` crate) — one-click install into Claude Code / Codex / OpenCode / Kiro / Copilot CLIs
|
||||
- No Node.js required — stdio transport via the desktop binary (`--mcp <path>`), plus a live HTTP endpoint (`127.0.0.1:<port>/mcp`) from the running app
|
||||
- Design automation from terminal: read, create, and modify `.op` files via any MCP-compatible agent
|
||||
- **Layered design workflow** — `design_skeleton` → `design_content` → `design_refine` for higher-fidelity multi-section designs
|
||||
|
|
@ -367,7 +366,7 @@ Supports three input methods: inline string, `@filepath` (read from file), or `-
|
|||
- Layered workflow — `design_skeleton` → `design_content` → `design_refine` with focused prompts per phase
|
||||
- Style Guides — 50+ built-in styles (glassmorphism, brutalist, retro, etc.) with tag-based fuzzy matching, wired into planning and generation
|
||||
- Multi-model capability profiles — auto-adapts thinking mode, effort, and prompt shape per model tier
|
||||
- Built-in Rust agent runtime + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini providers
|
||||
- Built-in Rust agent runtime + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Google Gemini API providers
|
||||
- Anthropic-format passthrough for Chinese LLM providers — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Git Integration**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Adapta-se automaticamente às capacidades de cada modelo. Claude recebe prompts
|
|||
|
||||
### 🔌 Servidor MCP
|
||||
|
||||
Instalação com um clique no Claude Code, Codex, Gemini, OpenCode, Kiro ou Copilot CLIs. Faça design pelo seu terminal — leia, crie e modifique arquivos `.op` através de qualquer agente compatível com MCP.
|
||||
Instalação com um clique no Claude Code, Codex, OpenCode, Kiro ou Copilot CLIs. Faça design pelo seu terminal — leia, crie e modifique arquivos `.op` através de qualquer agente compatível com MCP.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ Releases Rust com tag publicam uma única imagem de web host. As imagens TypeScr
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle e assets do CanvasKit |
|
||||
|
||||
A UI web expõe apenas perfis de agente integrados; as ferramentas Claude/Codex/OpenCode/Copilot/Gemini CLI não são incluídas nas imagens Docker.
|
||||
A UI web expõe apenas perfis de agente integrados; as ferramentas Claude/Codex/OpenCode/Copilot CLI não são incluídas nas imagens Docker.
|
||||
|
||||
**Executar:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | Conectar nas Configurações do Agente (`Cmd+,`) |
|
||||
| **OpenCode** | Conectar nas Configurações do Agente (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` e depois conectar nas Configurações do Agente (`Cmd+,`) |
|
||||
| **Gemini CLI** | Conectar nas Configurações do Agente (`Cmd+,`) |
|
||||
|
||||
**Perfis de Capacidade de Modelo** — adapta automaticamente prompts, modo de thinking e timeouts por nível de modelo. Modelos de nível completo (Claude) recebem prompts completos; nível padrão (GPT-4o, Gemini, DeepSeek) desativam thinking; nível básico (MiniMax, Qwen, Llama, Mistral) recebem prompts simplificados de JSON aninhado para máxima confiabilidade.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**Servidor MCP**
|
||||
|
||||
- Servidor MCP integrado (crate `op-mcp`) — instalação com um clique no Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
|
||||
- Servidor MCP integrado (crate `op-mcp`) — instalação com um clique no Claude Code / Codex / OpenCode / Kiro / Copilot CLIs
|
||||
- Não requer Node.js — transporte stdio via o binário desktop (`--mcp <path>`), além de um endpoint HTTP ativo (`127.0.0.1:<port>/mcp`) a partir do app em execução
|
||||
- Automação de design pelo terminal: leia, crie e modifique arquivos `.op` via qualquer agente compatível com MCP
|
||||
- **Fluxo de design em camadas** — `design_skeleton` → `design_content` → `design_refine` para designs multi-seção de maior fidelidade
|
||||
|
|
@ -229,7 +228,7 @@ Suporta strings inline, `@filepath` e stdin (`-`). Funciona com o app desktop, s
|
|||
- Fluxo em camadas — `design_skeleton` → `design_content` → `design_refine` com prompts focados por fase
|
||||
- Guias de estilo — mais de 50 estilos integrados (glassmorphism, brutalist, retro, etc.) com correspondência fuzzy baseada em tags, integrados ao planejamento e geração
|
||||
- Perfis de capacidade multi-modelo — adapta automaticamente o modo de pensamento, o esforço e a forma do prompt conforme o nível do modelo
|
||||
- Runtime de agente integrado (Rust) + provedores Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini
|
||||
- Runtime de agente integrado (Rust) + provedores Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot e Google Gemini API
|
||||
- Passthrough no formato Anthropic para provedores de LLMs chineses — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Integração com Git**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
### 🔌 MCP-сервер
|
||||
|
||||
Установка в один клик в Claude Code, Codex, Gemini, OpenCode, Kiro или Copilot CLI. Дизайн из терминала — чтение, создание и изменение файлов `.op` через любой MCP-совместимый агент.
|
||||
Установка в один клик в Claude Code, Codex, OpenCode, Kiro или Copilot CLI. Дизайн из терминала — чтение, создание и изменение файлов `.op` через любой MCP-совместимый агент.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ cargo run -p op-host-desktop
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle и ресурсы CanvasKit |
|
||||
|
||||
Веб-интерфейс показывает только встроенные профили агентов; инструменты Claude/Codex/OpenCode/Copilot/Gemini CLI не входят в Docker-образы.
|
||||
Веб-интерфейс показывает только встроенные профили агентов; инструменты Claude/Codex/OpenCode/Copilot CLI не входят в Docker-образы.
|
||||
|
||||
**Запуск:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | Подключить в настройках агента (`Cmd+,`) |
|
||||
| **OpenCode** | Подключить в настройках агента (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login`, затем подключить в настройках агента (`Cmd+,`) |
|
||||
| **Gemini CLI** | Подключить в настройках агента (`Cmd+,`) |
|
||||
|
||||
**Профили возможностей моделей** — автоматически адаптирует промпты, режим thinking и таймауты для каждого уровня моделей. Модели полного уровня (Claude) получают полные промпты; стандартного уровня (GPT-4o, Gemini, DeepSeek) — с отключённым thinking; базового уровня (MiniMax, Qwen, Llama, Mistral) — упрощённые промпты с вложенным JSON для максимальной надёжности.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP-сервер**
|
||||
|
||||
- Встроенный MCP-сервер (крейт `op-mcp`) — установка в один клик в Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
|
||||
- Встроенный MCP-сервер (крейт `op-mcp`) — установка в один клик в Claude Code / Codex / OpenCode / Kiro / Copilot CLI
|
||||
- Node.js не требуется — stdio-транспорт через десктопный бинарный файл (`--mcp <path>`), плюс работающий HTTP-эндпоинт (`127.0.0.1:<port>/mcp`) из запущенного приложения
|
||||
- Автоматизация дизайна из терминала: чтение, создание и изменение файлов `.op` через любой MCP-совместимый агент
|
||||
- **Послойный рабочий процесс** — `design_skeleton` → `design_content` → `design_refine` для дизайнов высокого качества с несколькими секциями
|
||||
|
|
@ -229,7 +228,7 @@ cat design.dsl | op design - # Передача через stdin
|
|||
- Послойный рабочий процесс — `design_skeleton` → `design_content` → `design_refine` со сфокусированными промптами на каждой фазе
|
||||
- Руководства по стилю — 50+ встроенных стилей (glassmorphism, brutalist, retro и т. д.) с нечётким сопоставлением по тегам, интегрированным в планирование и генерацию
|
||||
- Профили возможностей мультимодели — автоматически подстраивает режим размышления, уровень усилий и форму промпта в зависимости от уровня модели
|
||||
- Встроенный рантайм агентов (Rust) + провайдеры Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini
|
||||
- Встроенный рантайм агентов (Rust) + провайдеры Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot и Google Gemini API
|
||||
- Проброс формата Anthropic для китайских провайдеров LLM — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Интеграция с Git**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Orchestrator แบ่งหน้าที่ซับซ้อนออกเ
|
|||
|
||||
### 🔌 MCP Server
|
||||
|
||||
ติดตั้งได้ด้วยคลิกเดียวใน Claude Code, Codex, Gemini, OpenCode, Kiro หรือ Copilot CLIs ออกแบบจาก terminal ของคุณ — อ่าน สร้าง และแก้ไขไฟล์ `.op` ผ่าน agent ที่รองรับ MCP
|
||||
ติดตั้งได้ด้วยคลิกเดียวใน Claude Code, Codex, OpenCode, Kiro หรือ Copilot CLIs ออกแบบจาก terminal ของคุณ — อ่าน สร้าง และแก้ไขไฟล์ `.op` ผ่าน agent ที่รองรับ MCP
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ Rust release ที่มี tag จะเผยแพร่ image แบบ web
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle และ CanvasKit assets |
|
||||
|
||||
Web UI แสดงเฉพาะ built-in agent profiles เท่านั้น เครื่องมือ Claude/Codex/OpenCode/Copilot/Gemini CLI จะไม่ถูกบันเดิลใน Docker images
|
||||
Web UI แสดงเฉพาะ built-in agent profiles เท่านั้น เครื่องมือ Claude/Codex/OpenCode/Copilot CLI จะไม่ถูกบันเดิลใน Docker images
|
||||
|
||||
**รัน:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | เชื่อมต่อใน Agent Settings (`Cmd+,`) |
|
||||
| **OpenCode** | เชื่อมต่อใน Agent Settings (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` จากนั้นเชื่อมต่อใน Agent Settings (`Cmd+,`) |
|
||||
| **Gemini CLI** | เชื่อมต่อใน Agent Settings (`Cmd+,`) |
|
||||
|
||||
**โปรไฟล์ความสามารถของโมเดล** — ปรับ prompt, โหมด thinking และ timeout ตามระดับโมเดลโดยอัตโนมัติ โมเดลระดับเต็ม (Claude) ได้ prompt ครบถ้วน; โมเดลระดับมาตรฐาน (GPT-4o, Gemini, DeepSeek) ปิด thinking; โมเดลระดับพื้นฐาน (MiniMax, Qwen, Llama, Mistral) ได้ prompt แบบ nested-JSON ที่ย่อลงเพื่อความเสถียรสูงสุด
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP Server**
|
||||
|
||||
- MCP Server ในตัว (`op-mcp` crate) — ติดตั้งได้ด้วยคลิกเดียวใน Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
|
||||
- MCP Server ในตัว (`op-mcp` crate) — ติดตั้งได้ด้วยคลิกเดียวใน Claude Code / Codex / OpenCode / Kiro / Copilot CLIs
|
||||
- ไม่ต้องใช้ Node.js — stdio transport ผ่าน binary เดสก์ท็อป (`--mcp <path>`) พร้อม live HTTP endpoint (`127.0.0.1:<port>/mcp`) จากแอปที่กำลังทำงาน
|
||||
- การทำ Design automation จาก terminal: อ่าน สร้าง และแก้ไขไฟล์ `.op` ผ่าน agent ที่รองรับ MCP
|
||||
- **Layered design workflow** — `design_skeleton` → `design_content` → `design_refine` สำหรับดีไซน์หลายส่วนที่มีความละเอียดสูงขึ้น
|
||||
|
|
@ -229,7 +228,7 @@ cat design.dsl | op design - # Pipe จาก stdin
|
|||
- Layered workflow — `design_skeleton` → `design_content` → `design_refine` พร้อม prompt ที่เน้นเฉพาะในแต่ละเฟส
|
||||
- Style Guides — สไตล์ในตัวกว่า 50 แบบ (glassmorphism, brutalist, retro ฯลฯ) พร้อม fuzzy matching ตาม tag ใช้ในการวางแผนและการสร้าง
|
||||
- โปรไฟล์ความสามารถหลายโมเดล — ปรับโหมดการคิด ความพยายาม และรูปแบบ prompt อัตโนมัติตามระดับโมเดล
|
||||
- Agent runtime ในตัว (Rust) + ผู้ให้บริการ Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini
|
||||
- Agent runtime ในตัว (Rust) + ผู้ให้บริการ Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot และ Google Gemini API
|
||||
- Passthrough รูปแบบ Anthropic สำหรับผู้ให้บริการ LLM จีน — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**การเชื่อมต่อ Git**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Her modelin yeteneklerine otomatik olarak uyum sağlar. Claude tam promptlar ve
|
|||
|
||||
### 🔌 MCP Sunucusu
|
||||
|
||||
Claude Code, Codex, Gemini, OpenCode, Kiro veya Copilot CLI'larına tek tıkla kurulum. Terminalinizden tasarım yapın — herhangi bir MCP uyumlu ajan aracılığıyla `.op` dosyalarını okuyun, oluşturun ve düzenleyin.
|
||||
Claude Code, Codex, OpenCode, Kiro veya Copilot CLI'larına tek tıkla kurulum. Terminalinizden tasarım yapın — herhangi bir MCP uyumlu ajan aracılığıyla `.op` dosyalarını okuyun, oluşturun ve düzenleyin.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ Etiketli Rust sürümleri tek bir web host görüntüsü yayımlar. AI CLI'ları
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle ve CanvasKit varlıkları |
|
||||
|
||||
Web UI yalnızca yerleşik agent profillerini gösterir; Claude/Codex/OpenCode/Copilot/Gemini CLI araçları Docker görüntülerine dahil değildir.
|
||||
Web UI yalnızca yerleşik agent profillerini gösterir; Claude/Codex/OpenCode/Copilot CLI araçları Docker görüntülerine dahil değildir.
|
||||
|
||||
**Çalıştır:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | Ajan Ayarlarından bağlanın (`Cmd+,`) |
|
||||
| **OpenCode** | Ajan Ayarlarından bağlanın (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` ardından Ajan Ayarlarından bağlanın (`Cmd+,`) |
|
||||
| **Gemini CLI** | Ajan Ayarlarından bağlanın (`Cmd+,`) |
|
||||
|
||||
**Model Yetenek Profilleri** — promptları, düşünme modunu ve zaman aşımlarını model katmanına göre otomatik olarak uyarlar. Tam katman modeller (Claude) eksiksiz promptlar alır; standart katman (GPT-4o, Gemini, DeepSeek) düşünme modunu devre dışı bırakır; temel katman (MiniMax, Qwen, Llama, Mistral) maksimum güvenilirlik için basitleştirilmiş iç içe JSON promptları alır.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP Sunucusu**
|
||||
|
||||
- Yerleşik MCP sunucusu (`op-mcp` crate) — Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI'larına tek tıkla kurulum
|
||||
- Yerleşik MCP sunucusu (`op-mcp` crate) — Claude Code / Codex / OpenCode / Kiro / Copilot CLI'larına tek tıkla kurulum
|
||||
- Node.js gerekmez — masaüstü ikili dosyası aracılığıyla stdio aktarımı (`--mcp <path>`), ayrıca çalışan uygulamadan canlı bir HTTP uç noktası (`127.0.0.1:<port>/mcp`)
|
||||
- Terminalden tasarım otomasyonu: herhangi bir MCP uyumlu ajan aracılığıyla `.op` dosyalarını okuyun, oluşturun ve düzenleyin
|
||||
- **Katmanlı tasarım iş akışı** — daha yüksek kaliteli çok bölümlü tasarımlar için `design_skeleton` → `design_content` → `design_refine`
|
||||
|
|
@ -229,7 +228,7 @@ Satır içi metni, `@filepath` ve stdin'i (`-`) destekler. Masaüstü uygulama,
|
|||
- Katmanlı iş akışı — `design_skeleton` → `design_content` → `design_refine`, her aşamada odaklı prompt'lar
|
||||
- Stil Rehberleri — 50+ yerleşik stil (glassmorphism, brutalist, retro vb.), etiket tabanlı bulanık eşleştirme ile planlama ve üretime entegre
|
||||
- Çoklu model yetenek profilleri — model katmanına göre düşünme modunu, çabayı ve prompt biçimini otomatik olarak uyarlar
|
||||
- Yerleşik ajan çalışma ortamı (Rust) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini sağlayıcıları
|
||||
- Yerleşik ajan çalışma ortamı (Rust) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot ve Google Gemini API sağlayıcıları
|
||||
- Çinli LLM sağlayıcıları için Anthropic formatlı geçiş — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Git Entegrasyonu**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Tự động thích ứng với khả năng của từng mô hình. Claude nhậ
|
|||
|
||||
### 🔌 Máy chủ MCP
|
||||
|
||||
Cài đặt một cú nhấp vào Claude Code, Codex, Gemini, OpenCode, Kiro hoặc Copilot CLI. Thiết kế từ terminal — đọc, tạo và chỉnh sửa tệp `.op` thông qua bất kỳ tác nhân tương thích MCP nào.
|
||||
Cài đặt một cú nhấp vào Claude Code, Codex, OpenCode, Kiro hoặc Copilot CLI. Thiết kế từ terminal — đọc, tạo và chỉnh sửa tệp `.op` thông qua bất kỳ tác nhân tương thích MCP nào.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ Các bản phát hành Rust có tag chỉ phát hành một image web host. Các
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host, wasm bundle và tài nguyên CanvasKit |
|
||||
|
||||
Web UI chỉ hiển thị các built-in agent profiles; công cụ Claude/Codex/OpenCode/Copilot/Gemini CLI không được đóng gói trong Docker images.
|
||||
Web UI chỉ hiển thị các built-in agent profiles; công cụ Claude/Codex/OpenCode/Copilot CLI không được đóng gói trong Docker images.
|
||||
|
||||
**Chạy:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | Kết nối trong Cài đặt tác nhân (`Cmd+,`) |
|
||||
| **OpenCode** | Kết nối trong Cài đặt tác nhân (`Cmd+,`) |
|
||||
| **GitHub Copilot** | `copilot login` rồi kết nối trong Cài đặt tác nhân (`Cmd+,`) |
|
||||
| **Gemini CLI** | Kết nối trong Cài đặt tác nhân (`Cmd+,`) |
|
||||
|
||||
**Hồ sơ Năng lực Mô hình** — tự động thích ứng prompt, chế độ thinking và thời gian chờ theo từng cấp mô hình. Mô hình cấp đầy đủ (Claude) nhận prompt hoàn chỉnh; cấp tiêu chuẩn (GPT-4o, Gemini, DeepSeek) tắt thinking; cấp cơ bản (MiniMax, Qwen, Llama, Mistral) nhận prompt JSON lồng nhau đơn giản hóa để đảm bảo độ tin cậy tối đa.
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**Máy chủ MCP**
|
||||
|
||||
- Máy chủ MCP tích hợp sẵn (crate `op-mcp`) — cài đặt một cú nhấp vào Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
|
||||
- Máy chủ MCP tích hợp sẵn (crate `op-mcp`) — cài đặt một cú nhấp vào Claude Code / Codex / OpenCode / Kiro / Copilot CLI
|
||||
- Không cần Node.js — stdio transport qua tệp nhị phân desktop (`--mcp <path>`), cộng với một HTTP endpoint trực tiếp (`127.0.0.1:<port>/mcp`) từ ứng dụng đang chạy
|
||||
- Tự động hóa thiết kế từ terminal: đọc, tạo và chỉnh sửa các tệp `.op` qua bất kỳ tác nhân tương thích MCP nào
|
||||
- **Quy trình thiết kế phân lớp** — `design_skeleton` → `design_content` → `design_refine` cho thiết kế đa phần có độ trung thực cao hơn
|
||||
|
|
@ -229,7 +228,7 @@ Hỗ trợ chuỗi inline, `@filepath` và stdin (`-`). Hoạt động với ứ
|
|||
- Quy trình phân lớp — `design_skeleton` → `design_content` → `design_refine` với prompt tập trung cho từng giai đoạn
|
||||
- Style Guides — hơn 50 style tích hợp (glassmorphism, brutalist, retro, v.v.) với khớp mờ dựa trên tag, tích hợp vào lập kế hoạch và tạo
|
||||
- Hồ sơ năng lực đa mô hình — tự động điều chỉnh chế độ tư duy, nỗ lực và hình thức prompt theo cấp mô hình
|
||||
- Runtime tác nhân tích hợp (Rust) + nhà cung cấp Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini
|
||||
- Runtime tác nhân tích hợp (Rust) + nhà cung cấp Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot và Google Gemini API
|
||||
- Chuyển tiếp định dạng Anthropic cho các nhà cung cấp LLM Trung Quốc — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
|
||||
|
||||
**Tích hợp Git**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
### 🔌 MCP 伺服器
|
||||
|
||||
一鍵安裝至 Claude Code、Codex、Gemini、OpenCode、Kiro 或 Copilot CLI。從終端機進行設計 — 透過任意 MCP 相容的智能體讀取、建立和修改 `.op` 檔案。
|
||||
一鍵安裝至 Claude Code、Codex、OpenCode、Kiro 或 Copilot CLI。從終端機進行設計 — 透過任意 MCP 相容的智能體讀取、建立和修改 `.op` 檔案。
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ cargo run -p op-host-desktop
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host、wasm bundle 和 CanvasKit 資源 |
|
||||
|
||||
Web UI 僅公開內建 agent profiles;Docker 映像檔不再內建 Claude/Codex/OpenCode/Copilot/Gemini CLI 工具。
|
||||
Web UI 僅公開內建 agent profiles;Docker 映像檔不再內建 Claude/Codex/OpenCode/Copilot CLI 工具。
|
||||
|
||||
**執行:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | 在 Agent 設定中連接(`Cmd+,`) |
|
||||
| **OpenCode** | 在 Agent 設定中連接(`Cmd+,`) |
|
||||
| **GitHub Copilot** | 執行 `copilot login` 後在 Agent 設定中連接(`Cmd+,`) |
|
||||
| **Gemini CLI** | 在 Agent 設定中連接(`Cmd+,`) |
|
||||
|
||||
**模型能力設定檔** — 自動依據模型層級調整提示詞、思考模式和逾時設定。完整層級模型(Claude)獲得完整提示詞;標準層級(GPT-4o、Gemini、DeepSeek)停用思考模式;基礎層級(MiniMax、Qwen、Llama、Mistral)獲得精簡巢狀 JSON 提示詞,確保最大可靠性。
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP 伺服器**
|
||||
|
||||
- 內建 MCP 伺服器(`op-mcp` crate)— 一鍵安裝至 Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
|
||||
- 內建 MCP 伺服器(`op-mcp` crate)— 一鍵安裝至 Claude Code / Codex / OpenCode / Kiro / Copilot CLI
|
||||
- 無需 Node.js — 透過桌面二進位檔(`--mcp <path>`)提供 stdio 傳輸,並由執行中的應用程式提供即時 HTTP 端點(`127.0.0.1:<port>/mcp`)
|
||||
- 從終端機進行設計自動化:透過任意 MCP 相容的智能體讀取、建立和修改 `.op` 檔案
|
||||
- **分層設計工作流** — `design_skeleton` → `design_content` → `design_refine`,適用於高保真多區塊設計
|
||||
|
|
@ -229,7 +228,7 @@ cat design.dsl | op design - # 從 stdin 管道輸入
|
|||
- 分層工作流 — `design_skeleton` → `design_content` → `design_refine`,每個階段使用聚焦的提示詞
|
||||
- 風格指南 — 50+ 內建風格(glassmorphism、brutalist、retro 等),支援基於標籤的模糊匹配,並接入規劃與生成流程
|
||||
- 多模型能力設定檔 — 依模型層級自動適配思考模式、推理強度與提示詞形態
|
||||
- 內建智能體執行環境(Rust)+ Anthropic、Claude Agent SDK、OpenCode、Codex、Copilot、Gemini 提供商
|
||||
- 內建智能體執行環境(Rust)+ Anthropic、Claude Agent SDK、OpenCode、Codex、Copilot、Google Gemini API 提供商
|
||||
- 中國大型語言模型 Anthropic 格式透傳 — Kimi、Zhipu、GLM、DouBao、Ark、Bailian/DashScope、ModelScope、Coding Plans
|
||||
|
||||
**Git 整合**
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
### 🔌 MCP 服务器
|
||||
|
||||
一键安装到 Claude Code、Codex、Gemini、OpenCode、Kiro 或 Copilot CLI。从终端进行设计 — 通过任意 MCP 兼容的智能体读取、创建和修改 `.op` 文件。
|
||||
一键安装到 Claude Code、Codex、OpenCode、Kiro 或 Copilot CLI。从终端进行设计 — 通过任意 MCP 兼容的智能体读取、创建和修改 `.op` 文件。
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -125,7 +125,7 @@ cargo run -p op-host-desktop
|
|||
| --- | --- |
|
||||
| `ghcr.io/zseven-w/openpencil-web:vX.Y.Z` | Rust web host、wasm bundle 和 CanvasKit 资源 |
|
||||
|
||||
Web UI 只暴露内置 agent profiles;Docker 镜像不再内置 Claude/Codex/OpenCode/Copilot/Gemini CLI 工具。
|
||||
Web UI 只暴露内置 agent profiles;Docker 镜像不再内置 Claude/Codex/OpenCode/Copilot CLI 工具。
|
||||
|
||||
**运行:**
|
||||
|
||||
|
|
@ -161,7 +161,6 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
| **Codex CLI** | 在 Agent 设置中连接(`Cmd+,`) |
|
||||
| **OpenCode** | 在 Agent 设置中连接(`Cmd+,`) |
|
||||
| **GitHub Copilot** | 运行 `copilot login` 后在 Agent 设置中连接(`Cmd+,`) |
|
||||
| **Gemini CLI** | 在 Agent 设置中连接(`Cmd+,`) |
|
||||
|
||||
**模型能力配置** — 自动根据模型层级适配提示词、思考模式和超时时间。完整层级模型(Claude)获得完整提示词;标准层级模型(GPT-4o、Gemini、DeepSeek)关闭思考模式;基础层级模型(MiniMax、Qwen、Llama、Mistral)使用简化的嵌套 JSON 提示词以确保最大可靠性。
|
||||
|
||||
|
|
@ -169,7 +168,7 @@ docker run -p 3100:3100 openpencil-web-rust
|
|||
|
||||
**MCP 服务器**
|
||||
|
||||
- 内置 MCP 服务器(`op-mcp` crate)— 一键安装到 Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
|
||||
- 内置 MCP 服务器(`op-mcp` crate)— 一键安装到 Claude Code / Codex / OpenCode / Kiro / Copilot CLI
|
||||
- 无需 Node.js — 通过桌面应用二进制文件(`--mcp <path>`)使用 stdio 传输,运行中的应用还提供实时 HTTP 端点(`127.0.0.1:<port>/mcp`)
|
||||
- 从终端进行设计自动化:通过任意 MCP 兼容的智能体读取、创建和修改 `.op` 文件
|
||||
- **分层设计工作流** — `design_skeleton` → `design_content` → `design_refine`,实现更高保真度的多区块设计
|
||||
|
|
@ -229,7 +228,7 @@ cat design.dsl | op design - # 从 stdin 管道输入
|
|||
- 分层工作流 — `design_skeleton` → `design_content` → `design_refine`,每个阶段使用聚焦的提示词
|
||||
- 风格指南 — 50+ 内置风格(glassmorphism、brutalist、retro 等),支持基于标签的模糊匹配,并接入规划与生成流程
|
||||
- 多模型能力配置 — 按模型层级自动适配思考模式、推理强度与提示词形态
|
||||
- 内置智能体运行时(Rust)+ Anthropic、Claude Agent SDK、OpenCode、Codex、Copilot、Gemini 提供商
|
||||
- 内置智能体运行时(Rust)+ Anthropic、Claude Agent SDK、OpenCode、Codex、Copilot、Google Gemini API 提供商
|
||||
- 国产大模型 Anthropic 格式透传 — Kimi、Zhipu、GLM、DouBao、Ark、Bailian/DashScope、ModelScope、Coding Plans
|
||||
|
||||
**Git 集成**
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ Mutators on `Document`:
|
|||
| LocalePicker | TopBar Globe-button dropdown (15 native names + Check) | `locale_picker.rs` |
|
||||
| StatusBar | Floating bottom-right — zoom controls | `status_bar.rs` |
|
||||
| icons | lucide d-string library (50+ icons; 24×24 viewBox stroke art) | `icons.rs` |
|
||||
| brand_icons | Claude / OpenAI / Gemini / Copilot / OpenCode brand logos (filled SVG paths, non-24×24 viewBoxes) | `brand_icons.rs` |
|
||||
| brand_icons | Claude / OpenAI / Gemini / Copilot / Antigravity / Grok filled logos + OpenCode primitive logo | `brand_icons.rs` |
|
||||
| ColorPicker | HSV overlay (Cmd-Shift-C or fill/stroke swatch click) — sat/value box + hue strip + hex input | `color_picker.rs` |
|
||||
| LayerContextMenu | Right-click overlay on layer rows + page tabs (Rename / Duplicate / Delete / Group / Ungroup / Lock / Hide; subset on page tabs) | `layer_context_menu.rs` |
|
||||
| AgentSettings | Cmd+, modal — 880×640 with sidebar nav (Agents / MCP / Images / System) + scrollable right pane | `agent_settings_panel.rs` + `agent_settings_{i18n,images,mcp,system}.rs` |
|
||||
|
|
@ -286,8 +286,8 @@ Every input path that reasons about the canvas region MUST derive its rects from
|
|||
|
||||
`agent_settings_panel.rs` + 4 tab modules render an 880×640 modal opened from the TopBar agent chip or `Cmd+,`. Sidebar nav: Agents / MCP / Images / System. Right pane scrolls; modal paints last (over dim scrim) so it covers every other widget.
|
||||
|
||||
- **Agents** — `+ 添加服务商` and `+ 添加 Agent` actions in two empty-state sections, then 5 provider cards (Claude / Codex / OpenCode / GitHub Copilot / Gemini) with real brand logos from `widgets/brand_icons.rs`. Hovering a connected card swaps the green `✓ Connected` row for a red `断开连接` button; both lifecycle actions toggle `agent_settings.connected[i]`.
|
||||
- **MCP** — server status card with port input + Start/Stop button, then a 2×4 grid of CLI integration toggles (Claude Code / Codex / Gemini / OpenCode / Kiro / GitHub Copilot / Antigravity / Grok Build). Port input is editable (see "Settings input editing" below).
|
||||
- **Agents** — `+ 添加服务商` and `+ 添加 Agent` actions in two empty-state sections, then 6 provider cards (Claude / Codex / OpenCode / GitHub Copilot / Antigravity / Grok Build) with real brand logos from `widgets/brand_icons.rs`. Hovering a connected card swaps the green `✓ Connected` row for a red `断开连接` button; both lifecycle actions toggle `agent_settings.connected[i]`.
|
||||
- **MCP** — server status card with port input + Start/Stop button, then a grid of 7 CLI integration toggles (Claude Code / Codex / OpenCode / Kiro / GitHub Copilot / Antigravity / Grok Build). Port input is editable (see "Settings input editing" below).
|
||||
- **Images** — Image Search Ready/Not-configured indicator + collapsible Advanced section (Openverse OAuth Client ID / Secret + Register link + Test button), then Image Generation section with `+ Add` empty state.
|
||||
- **System** — read-only Auto-update status card (no updater backend wired yet — a togglable switch would lie to the user; the row paints as informational text).
|
||||
|
||||
|
|
@ -506,7 +506,7 @@ mcp_tests.rs (in crate root) Cross-cutting: stdio dispatch, parser invariants
|
|||
|
||||
### Host wiring
|
||||
|
||||
`openpencil-desktop --mcp <path>` (`crates/openpencil-desktop/src/mcp_serve.rs`) runs a JSON-RPC stdio MCP server backed by the .op file at `<path>`. External CLIs (Claude Code / Codex / Gemini / Copilot) spawn the binary in this mode to drive the Rust editor the same way they drive TS pen-mcp.
|
||||
`openpencil-desktop --mcp <path>` (`crates/op-host-desktop/src/mcp_serve.rs`) runs a JSON-RPC stdio MCP server backed by the .op file at `<path>`. External CLIs (Claude Code / Codex / OpenCode / Kiro / Copilot / Antigravity / Grok Build) spawn the binary in this mode to drive the Rust editor.
|
||||
|
||||
- Handshake: `initialize` returns protocol version + capabilities + serverInfo; `tools/list` enumerates all 21 tools with JSON inputSchemas; `notifications/initialized` + `ping` handled inline.
|
||||
- Per-call lifecycle: re-build the `ToolRegistry` against the live document (so read-tool snapshots reflect prior writes) → dispatch through `run_stdio_with_applier` → applier closure mutates the doc + saves to disk on each successful write.
|
||||
|
|
@ -535,9 +535,8 @@ real CLI agents, not a stub:
|
|||
transport-free trait; real impls live desktop-side:
|
||||
`chat_runtime.rs` (`BuiltInProvider`, agent-rs), `chat_claude.rs`
|
||||
(`ClaudeCodeProvider`, `anthropic-agent-sdk`), `chat_subprocess.rs`
|
||||
(`SubprocessProvider` for Claude/Gemini/Copilot), `chat_copilot.rs`,
|
||||
`chat_http_server.rs` (Codex/OpenCode — `for_cli` returns `None`,
|
||||
protocol unverified upstream).
|
||||
(`SubprocessProvider` for Codex/Antigravity/Grok Build),
|
||||
`chat_copilot.rs`, `chat_http_server.rs` (OpenCode).
|
||||
- `chat_session.rs` — `ChatSession` runs a turn on a worker thread
|
||||
(`ChatProvider::send` is a blocking iterator). `launch_if_pending`
|
||||
drains `pending_send` → `provider_for_agent(chat_selected_agent)`;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ When the spec says "horizontal scrolling cards", "swipeable row", "chip row", "m
|
|||
|
||||
### Preferred (MCP tool path): pick one of 3 narrow row tools
|
||||
|
||||
If you have access to MCP tools (external client: Claude Code / Codex / Gemini CLI / Cursor), call the tool matching what's in the row — all three produce the overflow-safe wrapper+clipContent+fit_content structure and cannot be made incorrectly by schema.
|
||||
If you have access to MCP tools (external client: Claude Code / Codex / Cursor), call the tool matching what's in the row — all three produce the overflow-safe wrapper+clipContent+fit_content structure and cannot be made incorrectly by schema.
|
||||
|
||||
- **`add_card_row_v0`** — items with `title` + optional `subtitle` + optional `icon` (workout cards, feature tiles, content cards). Default card size 140×160.
|
||||
- **`add_metric_row_v0`** — items with `label` + `value` + optional `icon` (dashboard stats: Steps/Kcal/Sleep/Revenue). Default tile size 120×100, value rendered 28/700.
|
||||
|
|
|
|||
|
|
@ -33,18 +33,16 @@ pub enum AgentProvider {
|
|||
CodexCli,
|
||||
OpenCode,
|
||||
GithubCopilot,
|
||||
GeminiCli,
|
||||
Antigravity,
|
||||
GrokBuild,
|
||||
}
|
||||
|
||||
impl AgentProvider {
|
||||
pub const ALL: [AgentProvider; 7] = [
|
||||
pub const ALL: [AgentProvider; 6] = [
|
||||
AgentProvider::ClaudeCode,
|
||||
AgentProvider::CodexCli,
|
||||
AgentProvider::OpenCode,
|
||||
AgentProvider::GithubCopilot,
|
||||
AgentProvider::GeminiCli,
|
||||
AgentProvider::Antigravity,
|
||||
AgentProvider::GrokBuild,
|
||||
];
|
||||
|
|
@ -54,7 +52,6 @@ impl AgentProvider {
|
|||
AgentProvider::CodexCli => "Codex CLI",
|
||||
AgentProvider::OpenCode => "OpenCode",
|
||||
AgentProvider::GithubCopilot => "GitHub Copilot",
|
||||
AgentProvider::GeminiCli => "Gemini CLI",
|
||||
AgentProvider::Antigravity => "Antigravity",
|
||||
AgentProvider::GrokBuild => "Grok Build",
|
||||
}
|
||||
|
|
@ -70,7 +67,6 @@ impl AgentProvider {
|
|||
AgentProvider::CodexCli => "settings.provider.codexCli",
|
||||
AgentProvider::OpenCode => "settings.provider.openCode",
|
||||
AgentProvider::GithubCopilot => "settings.provider.githubCopilot",
|
||||
AgentProvider::GeminiCli => "settings.provider.geminiCli",
|
||||
AgentProvider::Antigravity => "settings.provider.antigravity",
|
||||
AgentProvider::GrokBuild => "settings.provider.grokBuild",
|
||||
}
|
||||
|
|
@ -78,14 +74,13 @@ impl AgentProvider {
|
|||
}
|
||||
|
||||
/// Terminal-side MCP integrations the user can flip on/off. Order
|
||||
/// matches the product's MCP settings grid (Claude / Codex / Gemini /
|
||||
/// OpenCode / Kiro / Copilot / Antigravity / Grok Build) so the index is
|
||||
/// matches the product's MCP settings grid (Claude / Codex / OpenCode /
|
||||
/// Kiro / Copilot / Antigravity / Grok Build) so the index is
|
||||
/// reusable for both layout and `mcp_cli_enabled[i]`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum McpCli {
|
||||
ClaudeCode,
|
||||
Codex,
|
||||
Gemini,
|
||||
OpenCode,
|
||||
Kiro,
|
||||
GithubCopilot,
|
||||
|
|
@ -94,10 +89,9 @@ pub enum McpCli {
|
|||
}
|
||||
|
||||
impl McpCli {
|
||||
pub const ALL: [McpCli; 8] = [
|
||||
pub const ALL: [McpCli; 7] = [
|
||||
McpCli::ClaudeCode,
|
||||
McpCli::Codex,
|
||||
McpCli::Gemini,
|
||||
McpCli::OpenCode,
|
||||
McpCli::Kiro,
|
||||
McpCli::GithubCopilot,
|
||||
|
|
@ -108,7 +102,6 @@ impl McpCli {
|
|||
match self {
|
||||
McpCli::ClaudeCode => "Claude Code CLI",
|
||||
McpCli::Codex => "Codex CLI",
|
||||
McpCli::Gemini => "Gemini CLI",
|
||||
McpCli::OpenCode => "OpenCode CLI",
|
||||
McpCli::Kiro => "Kiro CLI",
|
||||
McpCli::GithubCopilot => "GitHub Copilot CLI",
|
||||
|
|
@ -147,13 +140,13 @@ pub enum SettingsFocus {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AgentSettings {
|
||||
pub tab: AgentSettingsTab,
|
||||
pub connected: [bool; 7],
|
||||
pub connected: [bool; 6],
|
||||
/// Vertical scroll offset of the right content pane in px.
|
||||
/// Clamped to `[0, content_height - viewport_height]` by the
|
||||
/// host on wheel input.
|
||||
pub scroll_y: f32,
|
||||
pub mcp_server: McpServer,
|
||||
pub mcp_cli_enabled: [bool; 8],
|
||||
pub mcp_cli_enabled: [bool; 7],
|
||||
pub images_advanced_open: bool,
|
||||
pub images_search_ready: bool,
|
||||
/// Currently-focused editable input on the modal. `None` while
|
||||
|
|
@ -177,10 +170,10 @@ impl Default for AgentSettings {
|
|||
tab: AgentSettingsTab::Agents,
|
||||
// Connection state is wired manually for now — no auth
|
||||
// backend exists, so every provider starts disconnected.
|
||||
connected: [false; 7],
|
||||
connected: [false; 6],
|
||||
scroll_y: 0.0,
|
||||
mcp_server: McpServer::default(),
|
||||
mcp_cli_enabled: [false; 8],
|
||||
mcp_cli_enabled: [false; 7],
|
||||
images_advanced_open: true,
|
||||
images_search_ready: true,
|
||||
focus: None,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
//! against the user's chosen `Provider` (Anthropic, OpenAI-compat,
|
||||
//! Ollama, ...). This is the OP-native agent.
|
||||
//! - **Subprocess(CliName)** — spawn an external CLI binary
|
||||
//! (Claude Code / Gemini / Copilot) and pipe line-delimited JSON
|
||||
//! over its stdin / stdout.
|
||||
//! (Claude Code / Copilot / Antigravity / Grok Build) and pipe
|
||||
//! line-delimited JSON over its stdin / stdout.
|
||||
//! - **HttpServer(CliName)** — spawn `codex serve` / `opencode serve`
|
||||
//! and hit its local HTTP/SSE endpoint with reqwest.
|
||||
//! - **Acp** — Agent Client Protocol (ndJSON over stdio); the
|
||||
|
|
@ -31,8 +31,6 @@ use std::sync::Arc;
|
|||
pub enum CliName {
|
||||
/// Anthropic's `claude` CLI (subprocess IPC).
|
||||
ClaudeCode,
|
||||
/// Google's `gemini` CLI (subprocess IPC).
|
||||
Gemini,
|
||||
/// GitHub Copilot CLI (subprocess IPC).
|
||||
Copilot,
|
||||
/// OpenAI Codex CLI (HTTP server mode).
|
||||
|
|
@ -49,7 +47,6 @@ impl CliName {
|
|||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
CliName::ClaudeCode => "Claude Code",
|
||||
CliName::Gemini => "Gemini",
|
||||
CliName::Copilot => "GitHub Copilot",
|
||||
CliName::Codex => "Codex",
|
||||
CliName::OpenCode => "OpenCode",
|
||||
|
|
@ -63,7 +60,6 @@ impl CliName {
|
|||
pub fn default_binary(self) -> &'static str {
|
||||
match self {
|
||||
CliName::ClaudeCode => "claude",
|
||||
CliName::Gemini => "gemini",
|
||||
// The standalone `copilot` CLI (the `gh-copilot` gh
|
||||
// extension is retired; the official SDK + model
|
||||
// discovery both target `copilot`).
|
||||
|
|
@ -76,14 +72,13 @@ impl CliName {
|
|||
}
|
||||
/// Which backend transport this CLI uses. Mirrors the table in
|
||||
/// [[project_agent_runtime]] memory:
|
||||
/// Claude/Gemini/Copilot = subprocess IPC; Codex/OpenCode = HTTP server.
|
||||
/// Claude/Copilot/Antigravity/Grok Build = subprocess IPC;
|
||||
/// Codex/OpenCode = HTTP server.
|
||||
pub fn backend(self) -> ChatProviderKind {
|
||||
match self {
|
||||
CliName::ClaudeCode
|
||||
| CliName::Gemini
|
||||
| CliName::Copilot
|
||||
| CliName::Antigravity
|
||||
| CliName::GrokBuild => ChatProviderKind::Subprocess(self),
|
||||
CliName::ClaudeCode | CliName::Copilot | CliName::Antigravity | CliName::GrokBuild => {
|
||||
ChatProviderKind::Subprocess(self)
|
||||
}
|
||||
CliName::Codex | CliName::OpenCode => ChatProviderKind::HttpServer(self),
|
||||
}
|
||||
}
|
||||
|
|
@ -285,8 +280,8 @@ pub struct ChatRequest {
|
|||
pub attachments: Vec<ChatAttachment>,
|
||||
/// Model id the user picked in the chat model picker (e.g.
|
||||
/// `gpt-5.5`, `claude-sonnet-4-6`). Each transport forwards it on
|
||||
/// its own knob (`--model` for Codex, `-m` for Gemini, SDK
|
||||
/// `options.model` for Claude Code / Copilot). `None` keeps the
|
||||
/// its own knob (`--model` for Codex / Antigravity, `-m` for
|
||||
/// Grok Build, SDK `options.model` for Claude Code / Copilot). `None` keeps the
|
||||
/// provider's own default — TS parity: every provider in
|
||||
/// `apps/web/server/api/ai/chat.ts` only sets the model when one
|
||||
/// was supplied.
|
||||
|
|
@ -438,16 +433,12 @@ mod tests {
|
|||
#[test]
|
||||
fn cli_name_backend_table_matches_architecture_memo() {
|
||||
// project_agent_runtime memory:
|
||||
// Subprocess IPC = Claude Code / Gemini / Copilot
|
||||
// Subprocess IPC = Claude Code / Copilot / Antigravity / Grok Build
|
||||
// HTTP server = Codex / OpenCode
|
||||
assert!(matches!(
|
||||
CliName::ClaudeCode.backend(),
|
||||
ChatProviderKind::Subprocess(_)
|
||||
));
|
||||
assert!(matches!(
|
||||
CliName::Gemini.backend(),
|
||||
ChatProviderKind::Subprocess(_)
|
||||
));
|
||||
assert!(matches!(
|
||||
CliName::Copilot.backend(),
|
||||
ChatProviderKind::Subprocess(_)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@
|
|||
".claude-plugin/plugin.json": "{\n \"name\": \"openpencil-skill\",\n \"description\": \"Design skill for OpenPencil — op CLI, batch DSL, MCP tools, PenNode schema, and UI design best practices\",\n \"version\": \"__OPENPENCIL_VERSION__\",\n \"author\": {\n \"name\": \"ZSeven-W\",\n \"email\": \"xkayshen@gmail.com\"\n },\n \"homepage\": \"https://github.com/zseven-w/openpencil-skill\",\n \"repository\": \"https://github.com/zseven-w/openpencil-skill\",\n \"license\": \"MIT\",\n \"keywords\": [\n \"design\",\n \"ui\",\n \"vector\",\n \"openpencil\",\n \"cli\",\n \"mcp\",\n \"dsl\"\n ]\n}\n",
|
||||
".claude-plugin/marketplace.json": "{\n \"name\": \"openpencil-skill\",\n \"description\": \"Design skill for OpenPencil — op CLI, batch DSL, MCP tools, and UI best practices\",\n \"owner\": {\n \"name\": \"ZSeven-W\",\n \"email\": \"xkayshen@gmail.com\"\n },\n \"plugins\": [\n {\n \"name\": \"openpencil-skill\",\n \"description\": \"Design skill for OpenPencil — op CLI, batch DSL, MCP tools, PenNode schema, and UI design best practices\",\n \"version\": \"__OPENPENCIL_VERSION__\",\n \"source\": \"./\",\n \"author\": {\n \"name\": \"ZSeven-W\",\n \"email\": \"xkayshen@gmail.com\"\n }\n }\n ]\n}\n",
|
||||
".cursor-plugin/plugin.json": "{\n \"name\": \"openpencil-skill\",\n \"displayName\": \"OpenPencil Design\",\n \"description\": \"Design skill for OpenPencil — op CLI, batch DSL, MCP tools, PenNode schema, and UI design best practices\",\n \"version\": \"__OPENPENCIL_VERSION__\",\n \"author\": {\n \"name\": \"ZSeven-W\",\n \"email\": \"xkayshen@gmail.com\"\n },\n \"homepage\": \"https://github.com/zseven-w/openpencil-skill\",\n \"repository\": \"https://github.com/zseven-w/openpencil-skill\",\n \"license\": \"MIT\",\n \"keywords\": [\n \"design\",\n \"ui\",\n \"vector\",\n \"openpencil\",\n \"cli\",\n \"mcp\",\n \"dsl\"\n ],\n \"skills\": \"./skills/\"\n}\n",
|
||||
"package.json": "{\n \"name\": \"openpencil-skill\",\n \"version\": \"__OPENPENCIL_VERSION__\",\n \"type\": \"module\"\n}\n",
|
||||
"GEMINI.md": "@./skills/openpencil-design/SKILL.md\n",
|
||||
"gemini-extension.json": "{\n \"name\": \"openpencil-skill\",\n \"description\": \"Design skill for OpenPencil — op CLI, batch DSL, MCP tools, and UI best practices\",\n \"version\": \"__OPENPENCIL_VERSION__\",\n \"contextFileName\": \"GEMINI.md\"\n}\n"
|
||||
"package.json": "{\n \"name\": \"openpencil-skill\",\n \"version\": \"__OPENPENCIL_VERSION__\",\n \"type\": \"module\"\n}\n"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
const BUNDLE_JSON: &str = include_str!("../assets/skill-bundle.json");
|
||||
const VERSION_SENTINEL: &str = "__OPENPENCIL_VERSION__";
|
||||
// Top-level bundle version plus five embedded plugin/package manifest versions.
|
||||
const EXPECTED_VERSION_SENTINEL_COUNT: usize = 6;
|
||||
// Top-level bundle version plus four embedded plugin/package manifest versions.
|
||||
const EXPECTED_VERSION_SENTINEL_COUNT: usize = 5;
|
||||
const REPO: &str = "zseven-w/openpencil-skill";
|
||||
const SKILL_NAME: &str = "openpencil-skill";
|
||||
|
||||
|
|
@ -21,7 +21,6 @@ enum Target {
|
|||
Claude,
|
||||
Codex,
|
||||
Cursor,
|
||||
Gemini,
|
||||
OpenCode,
|
||||
}
|
||||
|
||||
|
|
@ -31,10 +30,9 @@ impl Target {
|
|||
"claude" | "claude-code" | "claudecode" => Ok(Target::Claude),
|
||||
"codex" => Ok(Target::Codex),
|
||||
"cursor" => Ok(Target::Cursor),
|
||||
"gemini" | "gemini-cli" => Ok(Target::Gemini),
|
||||
"opencode" | "open-code" => Ok(Target::OpenCode),
|
||||
_ => Err(format!(
|
||||
"unknown target {raw:?}; available: claude, codex, cursor, gemini, opencode"
|
||||
"unknown target {raw:?}; available: claude, codex, cursor, opencode"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +42,6 @@ impl Target {
|
|||
Target::Claude => "claude",
|
||||
Target::Codex => "codex",
|
||||
Target::Cursor => "cursor",
|
||||
Target::Gemini => "gemini",
|
||||
Target::OpenCode => "opencode",
|
||||
}
|
||||
}
|
||||
|
|
@ -110,7 +107,7 @@ fn resolve_targets(
|
|||
let detected = detect_targets(home);
|
||||
if detected.is_empty() && matches!(action, Action::Install) {
|
||||
return Err(
|
||||
"no supported AI coding agents detected; pass --target claude|codex|cursor|gemini|opencode"
|
||||
"no supported AI coding agents detected; pass --target claude|codex|cursor|opencode"
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
|
@ -128,9 +125,6 @@ fn detect_targets(home: &Path) -> Vec<Target> {
|
|||
if home.join(".cursor").exists() {
|
||||
targets.push(Target::Cursor);
|
||||
}
|
||||
if command_exists("gemini") {
|
||||
targets.push(Target::Gemini);
|
||||
}
|
||||
if command_exists("opencode") {
|
||||
targets.push(Target::OpenCode);
|
||||
}
|
||||
|
|
@ -152,9 +146,6 @@ fn install_target(target: Target, home: &Path, bundle: &SkillBundle) -> Result<(
|
|||
Target::Claude => install_claude(home, bundle),
|
||||
Target::Codex => install_codex(home, bundle),
|
||||
Target::Cursor => write_bundle_to(&home.join(".cursor/plugins").join(SKILL_NAME), bundle),
|
||||
Target::Gemini => {
|
||||
write_bundle_to(&home.join(".gemini/extensions").join(SKILL_NAME), bundle)
|
||||
}
|
||||
Target::OpenCode => install_opencode(home, bundle),
|
||||
}
|
||||
}
|
||||
|
|
@ -164,7 +155,6 @@ fn uninstall_target(target: Target, home: &Path) -> Result<(), String> {
|
|||
Target::Claude => uninstall_claude(home),
|
||||
Target::Codex => uninstall_codex(home),
|
||||
Target::Cursor => remove_path(&home.join(".cursor/plugins").join(SKILL_NAME)),
|
||||
Target::Gemini => remove_path(&home.join(".gemini/extensions").join(SKILL_NAME)),
|
||||
Target::OpenCode => uninstall_opencode(home),
|
||||
}
|
||||
}
|
||||
|
|
@ -491,7 +481,6 @@ mod tests {
|
|||
".claude-plugin/plugin.json",
|
||||
".cursor-plugin/plugin.json",
|
||||
"package.json",
|
||||
"gemini-extension.json",
|
||||
] {
|
||||
let content = bundle
|
||||
.files
|
||||
|
|
@ -544,11 +533,26 @@ mod tests {
|
|||
fn bundle_renderer_rejects_partially_templated_versions() {
|
||||
let partial_template = BUNDLE_JSON.replacen(VERSION_SENTINEL, env!("CARGO_PKG_VERSION"), 1);
|
||||
let error = render_bundle_template(&partial_template, env!("CARGO_PKG_VERSION"))
|
||||
.expect_err("template with only five version sentinels should fail");
|
||||
.expect_err("partially rendered template should fail");
|
||||
let expected = format!("expected {EXPECTED_VERSION_SENTINEL_COUNT}");
|
||||
let found = format!("found {}", EXPECTED_VERSION_SENTINEL_COUNT - 1);
|
||||
|
||||
assert!(error.contains(&expected), "unexpected error: {error}");
|
||||
assert!(error.contains(&found), "unexpected error: {error}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_cli_is_not_an_install_target() {
|
||||
let error = super::Target::parse("gemini-cli")
|
||||
.expect_err("retired Gemini CLI integration must stay unavailable");
|
||||
|
||||
assert!(
|
||||
error.contains("unknown target"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
assert!(
|
||||
!error.contains("available: gemini"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ impl AgentProvider {
|
|||
Self::CodexCli => "codex-cli",
|
||||
Self::OpenCode => "open-code",
|
||||
Self::GithubCopilot => "github-copilot",
|
||||
Self::GeminiCli => "gemini-cli",
|
||||
Self::Antigravity => "antigravity",
|
||||
Self::GrokBuild => "grok-build",
|
||||
}
|
||||
|
|
@ -21,7 +20,6 @@ impl AgentProvider {
|
|||
"codex-cli" => Some(Self::CodexCli),
|
||||
"open-code" => Some(Self::OpenCode),
|
||||
"github-copilot" => Some(Self::GithubCopilot),
|
||||
"gemini-cli" => Some(Self::GeminiCli),
|
||||
"antigravity" => Some(Self::Antigravity),
|
||||
"grok-build" => Some(Self::GrokBuild),
|
||||
_ => None,
|
||||
|
|
@ -42,5 +40,6 @@ mod tests {
|
|||
);
|
||||
}
|
||||
assert_eq!(AgentProvider::from_wire_id("default"), None);
|
||||
assert_eq!(AgentProvider::from_wire_id("gemini-cli"), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ impl AgentSettingsTab {
|
|||
pub enum McpCli {
|
||||
ClaudeCode,
|
||||
Codex,
|
||||
Gemini,
|
||||
OpenCode,
|
||||
Kiro,
|
||||
GithubCopilot,
|
||||
|
|
@ -66,10 +65,9 @@ pub enum McpCli {
|
|||
}
|
||||
|
||||
impl McpCli {
|
||||
pub const ALL: [McpCli; 8] = [
|
||||
pub const ALL: [McpCli; 7] = [
|
||||
McpCli::ClaudeCode,
|
||||
McpCli::Codex,
|
||||
McpCli::Gemini,
|
||||
McpCli::OpenCode,
|
||||
McpCli::Kiro,
|
||||
McpCli::GithubCopilot,
|
||||
|
|
@ -81,7 +79,6 @@ impl McpCli {
|
|||
match self {
|
||||
McpCli::ClaudeCode => "Claude Code CLI",
|
||||
McpCli::Codex => "Codex CLI",
|
||||
McpCli::Gemini => "Gemini CLI",
|
||||
McpCli::OpenCode => "OpenCode CLI",
|
||||
McpCli::Kiro => "Kiro CLI",
|
||||
McpCli::GithubCopilot => "GitHub Copilot CLI",
|
||||
|
|
@ -416,7 +413,7 @@ pub struct ImageGenProfile {
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AgentSettings {
|
||||
pub tab: AgentSettingsTab,
|
||||
pub connected: [bool; 7],
|
||||
pub connected: [bool; 6],
|
||||
/// The embedding host's real MCP endpoint (e.g. the VS Code
|
||||
/// extension's McpProxy URL), delivered via the bridge `init`
|
||||
/// message. When set, the MCP tab's client-config card displays
|
||||
|
|
@ -426,7 +423,7 @@ pub struct AgentSettings {
|
|||
pub embed_mcp_url: Option<String>,
|
||||
/// Probe-derived per-provider connect status, indexed like
|
||||
/// `connected`. Runtime-only — not persisted.
|
||||
pub provider_connection: [ProviderConnection; 7],
|
||||
pub provider_connection: [ProviderConnection; 6],
|
||||
/// Connect-press request seam — the desktop host drains this
|
||||
/// into the async provider probe (`provider_probe_host.rs`).
|
||||
pub pending_provider_connect: Option<AgentProvider>,
|
||||
|
|
@ -450,7 +447,7 @@ pub struct AgentSettings {
|
|||
pub acp_agent_connection: BTreeMap<String, AcpAgentConnection>,
|
||||
pub scroll_y: jian_core::scroll::ScrollState,
|
||||
pub mcp_server: McpServer,
|
||||
pub mcp_cli_enabled: [bool; 8],
|
||||
pub mcp_cli_enabled: [bool; 7],
|
||||
pub mcp_client_config_copied_at_ms: Option<u64>,
|
||||
pub hover_agent_settings_close: bool,
|
||||
pub hover_mcp_server_button: bool,
|
||||
|
|
@ -502,7 +499,7 @@ impl Default for AgentSettings {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
tab: AgentSettingsTab::Agents,
|
||||
connected: [false; 7],
|
||||
connected: [false; 6],
|
||||
embed_mcp_url: None,
|
||||
provider_connection: Default::default(),
|
||||
pending_provider_connect: None,
|
||||
|
|
@ -520,7 +517,7 @@ impl Default for AgentSettings {
|
|||
acp_agent_connection: BTreeMap::new(),
|
||||
scroll_y: Default::default(),
|
||||
mcp_server: McpServer::default(),
|
||||
mcp_cli_enabled: [false; 8],
|
||||
mcp_cli_enabled: [false; 7],
|
||||
mcp_client_config_copied_at_ms: None,
|
||||
hover_agent_settings_close: false,
|
||||
hover_mcp_server_button: false,
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ pub struct ProviderConnection {
|
|||
/// Config-file path hint (TS `hintPath`).
|
||||
pub hint_path: Option<String>,
|
||||
/// CLI version string when the probe ran a version check
|
||||
/// (`codex --version` / `gemini --version`, mirroring the TS
|
||||
/// responsiveness gates).
|
||||
/// (for example, `codex --version`).
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +171,7 @@ impl AgentSettings {
|
|||
self.provider_verified_connected_at(Self::provider_index(provider))
|
||||
}
|
||||
|
||||
pub fn verified_connected_mask(&self) -> [bool; 7] {
|
||||
pub fn verified_connected_mask(&self) -> [bool; 6] {
|
||||
std::array::from_fn(|i| self.provider_verified_connected_at(i))
|
||||
}
|
||||
|
||||
|
|
@ -303,20 +302,20 @@ mod tests {
|
|||
fn not_installed_outcome_carries_install_guidance() {
|
||||
let mut s = AgentSettings::default();
|
||||
s.apply_provider_connect_outcome(
|
||||
AgentProvider::GeminiCli,
|
||||
AgentProvider::CodexCli,
|
||||
ProviderConnectOutcome {
|
||||
connected: false,
|
||||
not_installed: true,
|
||||
install_command: Some("npm install -g @anthropic-ai/gemini-cli".into()),
|
||||
error: Some("Gemini CLI not found".into()),
|
||||
install_command: Some("npm install -g @openai/codex".into()),
|
||||
error: Some("Codex CLI not found".into()),
|
||||
..ProviderConnectOutcome::default()
|
||||
},
|
||||
);
|
||||
let conn = &s.provider_connection[4];
|
||||
let conn = &s.provider_connection[1];
|
||||
assert!(conn.not_installed);
|
||||
assert_eq!(
|
||||
conn.install_command.as_deref(),
|
||||
Some("npm install -g @anthropic-ai/gemini-cli")
|
||||
Some("npm install -g @openai/codex")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +341,7 @@ mod tests {
|
|||
assert!(s.provider_verified_connected(AgentProvider::ClaudeCode));
|
||||
assert_eq!(
|
||||
s.verified_connected_mask(),
|
||||
[true, false, false, false, false, false, false]
|
||||
[true, false, false, false, false, false]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,18 +33,16 @@ pub enum AgentProvider {
|
|||
CodexCli,
|
||||
OpenCode,
|
||||
GithubCopilot,
|
||||
GeminiCli,
|
||||
Antigravity,
|
||||
GrokBuild,
|
||||
}
|
||||
|
||||
impl AgentProvider {
|
||||
pub const ALL: [AgentProvider; 7] = [
|
||||
pub const ALL: [AgentProvider; 6] = [
|
||||
AgentProvider::ClaudeCode,
|
||||
AgentProvider::CodexCli,
|
||||
AgentProvider::OpenCode,
|
||||
AgentProvider::GithubCopilot,
|
||||
AgentProvider::GeminiCli,
|
||||
AgentProvider::Antigravity,
|
||||
AgentProvider::GrokBuild,
|
||||
];
|
||||
|
|
@ -55,7 +53,6 @@ impl AgentProvider {
|
|||
AgentProvider::CodexCli => "Codex CLI",
|
||||
AgentProvider::OpenCode => "OpenCode",
|
||||
AgentProvider::GithubCopilot => "GitHub Copilot",
|
||||
AgentProvider::GeminiCli => "Gemini CLI",
|
||||
AgentProvider::Antigravity => "Antigravity",
|
||||
AgentProvider::GrokBuild => "Grok Build",
|
||||
}
|
||||
|
|
@ -68,7 +65,6 @@ impl AgentProvider {
|
|||
AgentProvider::CodexCli => "settings.provider.codexCli",
|
||||
AgentProvider::OpenCode => "settings.provider.openCode",
|
||||
AgentProvider::GithubCopilot => "settings.provider.githubCopilot",
|
||||
AgentProvider::GeminiCli => "settings.provider.geminiCli",
|
||||
AgentProvider::Antigravity => "settings.provider.antigravity",
|
||||
AgentProvider::GrokBuild => "settings.provider.grokBuild",
|
||||
}
|
||||
|
|
@ -544,7 +540,7 @@ impl ChatState {
|
|||
///
|
||||
/// [`available_models`]: ChatState::available_models
|
||||
/// [`discovered_models`]: ChatState::discovered_models
|
||||
pub fn rebuild_available_models(&mut self, connected: &[bool; 7]) {
|
||||
pub fn rebuild_available_models(&mut self, connected: &[bool; 6]) {
|
||||
let prev = self.available_models.get(self.selected_model).cloned();
|
||||
self.available_models = self
|
||||
.discovered_models
|
||||
|
|
@ -1464,7 +1460,7 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
// Only Claude Code (index 0 of AgentProvider::ALL) connected.
|
||||
let mut connected = [false; 7];
|
||||
let mut connected = [false; 6];
|
||||
connected[0] = true;
|
||||
chat.rebuild_available_models(&connected);
|
||||
assert_eq!(chat.available_models.len(), 2);
|
||||
|
|
@ -1483,7 +1479,7 @@ mod tests {
|
|||
],
|
||||
..Default::default()
|
||||
};
|
||||
let mut connected = [false; 7];
|
||||
let mut connected = [false; 6];
|
||||
connected[0] = true; // Claude
|
||||
connected[1] = true; // Codex
|
||||
chat.rebuild_available_models(&connected);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ use crate::agent_settings::{
|
|||
AcpConnectionType, AgentSettings, AgentSettingsTab, BuiltinAgentConfig, BuiltinAgentKind,
|
||||
ImageGenProvider, ImageTestStatus, McpCli,
|
||||
};
|
||||
use crate::agent_settings_builtin_presets::BuiltinAgentPresetKey;
|
||||
use crate::agent_settings_builtin_presets::{builtin_agent_preset, BuiltinAgentPresetKey};
|
||||
|
||||
#[test]
|
||||
fn default_settings_are_quiescent() {
|
||||
let s = AgentSettings::default();
|
||||
assert_eq!(s.tab, AgentSettingsTab::Agents);
|
||||
assert_eq!(s.connected, [false; 7]);
|
||||
assert_eq!(s.connected, [false; 6]);
|
||||
assert!(s.builtin_agents.is_empty());
|
||||
assert!(s.builtin_agent_draft.is_none());
|
||||
assert!(s.acp_agent_draft.is_none());
|
||||
|
|
@ -31,7 +31,7 @@ fn default_settings_are_quiescent() {
|
|||
#[test]
|
||||
fn tab_and_cli_arrays_cover_all_variants() {
|
||||
assert_eq!(AgentSettingsTab::ALL.len(), 6);
|
||||
assert_eq!(McpCli::ALL.len(), 8);
|
||||
assert_eq!(McpCli::ALL.len(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -142,6 +142,19 @@ fn add_builtin_agent_prefills_ts_provider_presets_first() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_api_preset_remains_available_after_cli_retirement() {
|
||||
let preset = builtin_agent_preset(BuiltinAgentPresetKey::Gemini);
|
||||
|
||||
assert_eq!(preset.display_name, "Google Gemini");
|
||||
assert_eq!(preset.kind, BuiltinAgentKind::OpenAiCompat);
|
||||
assert_eq!(preset.model, "gemini-3-flash-preview");
|
||||
assert_eq!(
|
||||
preset.base_url,
|
||||
"https://generativelanguage.googleapis.com/v1beta/openai"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ark_coding_builtin_agent_infers_ark_coding_preset() {
|
||||
let mut s = AgentSettings::default();
|
||||
|
|
|
|||
|
|
@ -609,12 +609,16 @@ fn select_chat_model_picks_model_and_syncs_agent() {
|
|||
let mut s = sample();
|
||||
s.chat.available_models = vec![
|
||||
crate::ModelEntry::new(crate::AgentProvider::ClaudeCode, "claude", "Claude"),
|
||||
crate::ModelEntry::new(crate::AgentProvider::GeminiCli, "gemini", "Gemini"),
|
||||
crate::ModelEntry::new(
|
||||
crate::AgentProvider::Antigravity,
|
||||
"antigravity",
|
||||
"Antigravity",
|
||||
),
|
||||
];
|
||||
s.editor_ui.chat_model_picker.open = true;
|
||||
s.select_chat_model(1);
|
||||
assert_eq!(s.chat.selected_model, 1);
|
||||
// GeminiCli is index 4 in AgentProvider::ALL.
|
||||
// Antigravity is index 4 in AgentProvider::ALL.
|
||||
assert_eq!(s.editor_ui.chat_selected_agent, 4);
|
||||
// Picker closes on selection.
|
||||
assert!(!s.editor_ui.chat_model_picker.open);
|
||||
|
|
@ -667,7 +671,7 @@ fn rebuild_chat_models_syncs_agent_to_selected_model_provider() {
|
|||
"gpt-5.5",
|
||||
"GPT-5.5",
|
||||
)];
|
||||
s.editor_ui.agent_settings.connected = [false, true, false, false, false, false, false];
|
||||
s.editor_ui.agent_settings.connected = [false, true, false, false, false, false];
|
||||
s.editor_ui.agent_settings.provider_connection[1].phase =
|
||||
crate::agent_settings::ProviderConnectPhase::Connected;
|
||||
s.editor_ui.chat_selected_agent = 0;
|
||||
|
|
@ -682,7 +686,7 @@ fn rebuild_chat_models_syncs_agent_to_selected_model_provider() {
|
|||
fn rebuild_chat_models_does_not_invent_cli_models_without_discovery() {
|
||||
let mut s = sample();
|
||||
s.chat.discovered_models.clear();
|
||||
s.editor_ui.agent_settings.connected = [false, true, false, false, false, false, false];
|
||||
s.editor_ui.agent_settings.connected = [false, true, false, false, false, false];
|
||||
|
||||
s.rebuild_chat_models();
|
||||
|
||||
|
|
|
|||
|
|
@ -166,10 +166,10 @@ fn failed_probe_paints_error_in_destructive_red() {
|
|||
fn not_installed_card_paints_amber_install_guidance() {
|
||||
let mut state = EditorState::default();
|
||||
let settings = &mut state.editor_ui.agent_settings;
|
||||
settings.provider_connection[4].phase = ProviderConnectPhase::Error;
|
||||
settings.provider_connection[4].not_installed = true;
|
||||
settings.provider_connection[4].install_command =
|
||||
Some("npm install -g @anthropic-ai/gemini-cli".to_string());
|
||||
settings.provider_connection[1].phase = ProviderConnectPhase::Error;
|
||||
settings.provider_connection[1].not_installed = true;
|
||||
settings.provider_connection[1].install_command =
|
||||
Some("npm install -g @openai/codex".to_string());
|
||||
let capture = paint_panel(&mut state);
|
||||
let (text, color) = find_text(&capture, "Not installed")
|
||||
.expect("not-installed card should show install guidance");
|
||||
|
|
@ -177,7 +177,7 @@ fn not_installed_card_paints_amber_install_guidance() {
|
|||
// install command may ellipsize at the tail; the actionable prefix must
|
||||
// still paint so the user knows what to run.
|
||||
assert!(
|
||||
text.contains("npm install -g @anthropic-ai/gemin"),
|
||||
text.contains("npm install -g @openai/codex"),
|
||||
"guidance line should carry the install command prefix, got: {text}"
|
||||
);
|
||||
let amber = (Color {
|
||||
|
|
|
|||
|
|
@ -81,13 +81,6 @@ pub(super) fn paint_agent_card(
|
|||
AVATAR_ICON,
|
||||
theme.foreground,
|
||||
),
|
||||
AgentProvider::GeminiCli => paint_brand_logo(
|
||||
cx.backend,
|
||||
BrandLogo::Gemini,
|
||||
icon_top_left,
|
||||
AVATAR_ICON,
|
||||
theme.foreground,
|
||||
),
|
||||
AgentProvider::Antigravity => paint_brand_logo(
|
||||
cx.backend,
|
||||
BrandLogo::Antigravity,
|
||||
|
|
|
|||
|
|
@ -608,9 +608,6 @@ pub fn paint_provider_logo(
|
|||
AgentProvider::CodexCli => {
|
||||
paint_brand_logo(cx.backend, BrandLogo::OpenAI, top_left, size, color)
|
||||
}
|
||||
AgentProvider::GeminiCli => {
|
||||
paint_brand_logo(cx.backend, BrandLogo::Gemini, top_left, size, color)
|
||||
}
|
||||
AgentProvider::GithubCopilot => {
|
||||
paint_brand_logo(cx.backend, BrandLogo::Copilot, top_left, size, color)
|
||||
}
|
||||
|
|
@ -630,7 +627,6 @@ pub(super) fn provider_label(provider: AgentProvider) -> &'static str {
|
|||
match provider {
|
||||
AgentProvider::ClaudeCode => "ANTHROPIC",
|
||||
AgentProvider::CodexCli => "OPENAI",
|
||||
AgentProvider::GeminiCli => "GOOGLE GEMINI",
|
||||
AgentProvider::GithubCopilot => "GITHUB COPILOT",
|
||||
AgentProvider::OpenCode => "OPENCODE",
|
||||
AgentProvider::Antigravity => "ANTIGRAVITY",
|
||||
|
|
@ -643,7 +639,6 @@ fn group_label(provider: AgentProvider, builtin: bool) -> &'static str {
|
|||
match provider {
|
||||
AgentProvider::ClaudeCode => "ANTHROPIC API KEY",
|
||||
AgentProvider::CodexCli => "OPENAI API KEY",
|
||||
AgentProvider::GeminiCli => "GEMINI API KEY",
|
||||
AgentProvider::GithubCopilot => "COPILOT API KEY",
|
||||
AgentProvider::OpenCode => "OPENCODE API KEY",
|
||||
AgentProvider::Antigravity => "ANTIGRAVITY API KEY",
|
||||
|
|
|
|||
|
|
@ -378,8 +378,7 @@ fn builtin_group_header_falls_back_to_generic_label_without_retained_name() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_provider_group_label_matches_ts_provider_name() {
|
||||
assert_eq!(provider_label(AgentProvider::GeminiCli), "GOOGLE GEMINI");
|
||||
fn newer_provider_group_labels_match_their_provider_names() {
|
||||
assert_eq!(provider_label(AgentProvider::Antigravity), "ANTIGRAVITY");
|
||||
assert_eq!(provider_label(AgentProvider::GrokBuild), "GROK BUILD");
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -106,7 +106,7 @@ pub struct TopBar {
|
|||
pub agent_count: u32,
|
||||
/// Per-provider connect state, indexed by `AgentProvider::ALL`.
|
||||
/// The active chip paints one brand icon per connected provider.
|
||||
pub connected: [bool; 7],
|
||||
pub connected: [bool; 6],
|
||||
/// Number of enabled MCP CLI integrations — the `· N MCP` half
|
||||
/// of the chip status.
|
||||
pub mcp_count: u32,
|
||||
|
|
@ -164,7 +164,7 @@ impl TopBar {
|
|||
file_name: file_name.into(),
|
||||
edited: false,
|
||||
agent_count: 1,
|
||||
connected: [false; 7],
|
||||
connected: [false; 6],
|
||||
mcp_count: 0,
|
||||
theme: Theme::dark(),
|
||||
label_edited: "",
|
||||
|
|
|
|||
|
|
@ -325,10 +325,10 @@ fn agent_chip_counts_ready_builtin_agents() {
|
|||
fn chip_with_only_builtin_agents_reserves_no_icon_cluster() {
|
||||
let mut bar = TopBar::new("test.op");
|
||||
bar.agent_count = 4;
|
||||
bar.connected = [false; 7];
|
||||
bar.connected = [false; 6];
|
||||
assert_eq!(bar.agent_icons_span(), 0.0, "no painted icons, no span");
|
||||
|
||||
bar.connected = [true, false, false, false, false, false, false];
|
||||
bar.connected = [true, false, false, false, false, false];
|
||||
assert!(
|
||||
bar.agent_icons_span() > 0.0,
|
||||
"a connected CLI provider brings the cluster back"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ struct PersistedAgentConnections {
|
|||
|
||||
/// Persist the currently-connected provider ids. Best-effort: a failed
|
||||
/// write must never break the probe flow.
|
||||
pub(crate) fn save(connected: &[bool; 7]) {
|
||||
pub(crate) fn save(connected: &[bool; 6]) {
|
||||
let value = PersistedAgentConnections {
|
||||
connected: AgentProvider::ALL
|
||||
.iter()
|
||||
|
|
@ -42,6 +42,10 @@ pub(crate) fn load() -> Vec<AgentProvider> {
|
|||
let Ok(Some(value)) = op_config_store::read_json::<PersistedAgentConnections>(FILE) else {
|
||||
return Vec::new();
|
||||
};
|
||||
providers_from_persisted(&value)
|
||||
}
|
||||
|
||||
fn providers_from_persisted(value: &PersistedAgentConnections) -> Vec<AgentProvider> {
|
||||
AgentProvider::ALL
|
||||
.iter()
|
||||
.copied()
|
||||
|
|
@ -54,12 +58,19 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn round_trips_connected_providers_through_json() {
|
||||
fn legacy_gemini_cli_entry_is_ignored_without_hiding_supported_providers() {
|
||||
let value = PersistedAgentConnections {
|
||||
connected: vec!["Claude Code".into(), "Gemini CLI".into()],
|
||||
connected: vec![
|
||||
"Claude Code".into(),
|
||||
"Gemini CLI".into(),
|
||||
"Antigravity".into(),
|
||||
],
|
||||
};
|
||||
let json = serde_json::to_string(&value).expect("serialize");
|
||||
let back: PersistedAgentConnections = serde_json::from_str(&json).expect("parse");
|
||||
assert_eq!(back.connected, vec!["Claude Code", "Gemini CLI"]);
|
||||
assert_eq!(
|
||||
providers_from_persisted(&back),
|
||||
vec![AgentProvider::ClaudeCode, AgentProvider::Antigravity]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -644,8 +644,8 @@ fn near(a: f64, b: f64) -> bool {
|
|||
|
||||
/// Build the `ChatProvider` for an agent index (into
|
||||
/// `AgentProvider::ALL`: 0 ClaudeCode, 1 CodexCli, 2 OpenCode,
|
||||
/// 3 GithubCopilot, 4 GeminiCli, 5 Antigravity, 6 GrokBuild). Claude Code uses its dedicated
|
||||
/// SDK adapter; Codex / Gemini use the subprocess transport; Copilot
|
||||
/// 3 GithubCopilot, 4 Antigravity, 5 GrokBuild). Claude Code uses its dedicated
|
||||
/// SDK adapter; Codex uses the subprocess transport; Copilot
|
||||
/// rides the official SDK; OpenCode chats over its local HTTP server
|
||||
/// (`chat_http_server.rs`).
|
||||
///
|
||||
|
|
@ -671,10 +671,8 @@ fn provider_for_agent(agent_idx: usize, chat_session: bool) -> Option<Box<dyn Ch
|
|||
} else {
|
||||
CopilotProvider::new()
|
||||
})),
|
||||
4 => SubprocessProvider::for_cli(CliName::Gemini)
|
||||
.map(|p| Box::new(p) as Box<dyn ChatProvider>),
|
||||
5 => subprocess_provider(CliName::Antigravity, chat_session),
|
||||
6 => subprocess_provider(CliName::GrokBuild, chat_session),
|
||||
4 => subprocess_provider(CliName::Antigravity, chat_session),
|
||||
5 => subprocess_provider(CliName::GrokBuild, chat_session),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -346,9 +346,9 @@ fn selected_cli_model_is_none_when_entry_provider_diverges_from_routed_agent() {
|
|||
"GPT-5.5",
|
||||
)];
|
||||
host.editor_state_mut().select_chat_model(0);
|
||||
// Force a divergence: route Gemini while the selected entry
|
||||
// Force a divergence: route Antigravity while the selected entry
|
||||
// still belongs to Codex. The Codex model id must NOT be
|
||||
// passed to the Gemini CLI.
|
||||
// passed to Antigravity.
|
||||
host.editor_state_mut().editor_ui.chat_selected_agent = 4;
|
||||
assert!(selected_cli_model_id(&host).is_none());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ struct DesktopApp {
|
|||
last_saved_pencil_cursor: Option<op_editor_core::PencilCursorStyle>,
|
||||
/// Last persisted `connected` flags — any change (Connect landing,
|
||||
/// Disconnect press in the widget layer) writes through to the store.
|
||||
last_saved_connections: Option<[bool; 7]>,
|
||||
last_saved_connections: Option<[bool; 6]>,
|
||||
/// In-flight ACP-agent connect probe (Settings → Agents → ACP
|
||||
/// Connect), drained by `drain_acp_agent_connect`.
|
||||
acp_agent_connect_job: Option<acp_agent_probe_host::AcpAgentConnectJob>,
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ pub(crate) fn set_cli_enabled(cli: McpCli, enabled: bool, port: u16) -> Result<P
|
|||
set_cli_enabled_at_path(cli, enabled, port, path)
|
||||
}
|
||||
|
||||
pub(crate) fn detect_enabled_clis() -> [bool; 8] {
|
||||
pub(crate) fn detect_enabled_clis() -> [bool; 7] {
|
||||
let Some(home) = dirs::home_dir() else {
|
||||
return [false; 8];
|
||||
return [false; 7];
|
||||
};
|
||||
detect_enabled_clis_for_home(&home, true)
|
||||
}
|
||||
|
|
@ -50,12 +50,12 @@ pub(crate) fn set_cli_enabled_at_home(
|
|||
}
|
||||
|
||||
/// Like [`detect_enabled_clis`] but against an explicit home dir (no env).
|
||||
pub(crate) fn detect_enabled_clis_at_home(home: &Path) -> [bool; 8] {
|
||||
pub(crate) fn detect_enabled_clis_at_home(home: &Path) -> [bool; 7] {
|
||||
detect_enabled_clis_for_home(home, false)
|
||||
}
|
||||
|
||||
fn detect_enabled_clis_for_home(home: &Path, use_env: bool) -> [bool; 8] {
|
||||
let mut flags = [false; 8];
|
||||
fn detect_enabled_clis_for_home(home: &Path, use_env: bool) -> [bool; 7] {
|
||||
let mut flags = [false; 7];
|
||||
for (idx, cli) in McpCli::ALL.iter().copied().enumerate() {
|
||||
flags[idx] = if cli == McpCli::Antigravity {
|
||||
antigravity_config_has_openpencil(&config_path(cli, home, use_env))
|
||||
|
|
@ -80,11 +80,9 @@ fn set_cli_enabled_at_path(
|
|||
McpCli::Antigravity => {
|
||||
return Err("Antigravity configuration requires a home directory".into())
|
||||
}
|
||||
McpCli::ClaudeCode
|
||||
| McpCli::Gemini
|
||||
| McpCli::OpenCode
|
||||
| McpCli::Kiro
|
||||
| McpCli::GithubCopilot => update_json_config(&path, enabled, port)?,
|
||||
McpCli::ClaudeCode | McpCli::OpenCode | McpCli::Kiro | McpCli::GithubCopilot => {
|
||||
update_json_config(&path, enabled, port)?
|
||||
}
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
|
@ -98,11 +96,9 @@ fn cli_config_has_openpencil(cli: McpCli, path: &Path) -> bool {
|
|||
.map(|text| grok_config_has_openpencil(&text))
|
||||
.unwrap_or(false),
|
||||
McpCli::Antigravity => antigravity_config_has_openpencil(path),
|
||||
McpCli::ClaudeCode
|
||||
| McpCli::Gemini
|
||||
| McpCli::OpenCode
|
||||
| McpCli::Kiro
|
||||
| McpCli::GithubCopilot => json_config_has_openpencil(path),
|
||||
McpCli::ClaudeCode | McpCli::OpenCode | McpCli::Kiro | McpCli::GithubCopilot => {
|
||||
json_config_has_openpencil(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +115,6 @@ fn config_path(cli: McpCli, home: &Path, use_env: bool) -> PathBuf {
|
|||
home.join(".codex").join("config.toml")
|
||||
}
|
||||
}
|
||||
McpCli::Gemini => home.join(".gemini").join("settings.json"),
|
||||
McpCli::OpenCode => home.join(".opencode").join("config.json"),
|
||||
McpCli::Kiro => home.join(".kiro").join("settings.json"),
|
||||
McpCli::GithubCopilot => home.join(".config").join("github-copilot").join("mcp.json"),
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ impl DesktopApp {
|
|||
}
|
||||
changed |= self.reconcile_mcp_server_from_settings();
|
||||
if any_cli_enabled {
|
||||
changed |= self.reconcile_mcp_cli_integrations(Some(([false; 8], port)));
|
||||
changed |= self.reconcile_mcp_cli_integrations(Some(([false; 7], port)));
|
||||
}
|
||||
if self.mcp_server_active() {
|
||||
changed |= self.request_redraw(false);
|
||||
|
|
@ -226,7 +226,7 @@ impl DesktopApp {
|
|||
|
||||
pub(crate) fn reconcile_mcp_cli_integrations(
|
||||
&mut self,
|
||||
before: Option<([bool; 8], u16)>,
|
||||
before: Option<([bool; 7], u16)>,
|
||||
) -> bool {
|
||||
let Some((before_flags, before_port)) = before else {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ use op_host_services::mcp_serve::{run, run_http};
|
|||
/// - `--mcp-http <port> <path>` — Streamable-HTTP MCP server.
|
||||
/// - `--serve-web <port> [doc] [--host <addr>]` — headless web-canvas daemon.
|
||||
///
|
||||
/// External CLIs (Claude Code / Codex / Gemini / Copilot) spawn the
|
||||
/// binary in these modes to drive the Rust editor the same way they
|
||||
/// drive TS pen-mcp.
|
||||
/// External CLIs (Claude Code / Codex / OpenCode / Kiro / Copilot /
|
||||
/// Antigravity / Grok Build) spawn the binary in these modes to drive
|
||||
/// the Rust editor.
|
||||
pub fn run_cli_if_requested() -> bool {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let Some(first) = args.next() else {
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ mod tests {
|
|||
|
||||
fn reset_settings(app: &mut DesktopApp) {
|
||||
let es = app.host.editor_state_mut();
|
||||
es.editor_ui.agent_settings.connected = [false; 7];
|
||||
es.editor_ui.agent_settings.connected = [false; 6];
|
||||
es.editor_ui.agent_settings.provider_connection = Default::default();
|
||||
es.editor_ui.agent_settings.pending_provider_connect = None;
|
||||
es.editor_ui.agent_settings.builtin_agents.clear();
|
||||
|
|
@ -256,9 +256,9 @@ mod tests {
|
|||
.chat
|
||||
.discovered_models
|
||||
.push(op_editor_core::ModelEntry::new(
|
||||
AgentProvider::GeminiCli,
|
||||
"gemini-2.5-pro",
|
||||
"Gemini 2.5 Pro",
|
||||
AgentProvider::Antigravity,
|
||||
"default",
|
||||
"Antigravity Default",
|
||||
));
|
||||
app.host
|
||||
.editor_state_mut()
|
||||
|
|
@ -297,15 +297,15 @@ mod tests {
|
|||
settings.provider_connection[0].info.as_deref(),
|
||||
Some("Connected via pro (a@b.c)")
|
||||
);
|
||||
// Claude's entry spliced in, sorted before the seeded Gemini
|
||||
// entry (ALL order), which survived.
|
||||
// Claude's entry spliced in, sorted before the seeded
|
||||
// Antigravity entry (ALL order), which survived.
|
||||
let values: Vec<&str> = es
|
||||
.chat
|
||||
.discovered_models
|
||||
.iter()
|
||||
.map(|m| m.value.as_str())
|
||||
.collect();
|
||||
assert_eq!(values, ["claude-sonnet-4-6", "gemini-2.5-pro"]);
|
||||
assert_eq!(values, ["claude-sonnet-4-6", "default"]);
|
||||
// Connected → the picker lists the Claude model.
|
||||
assert!(es
|
||||
.chat
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ fn seed_two_chat_models(host: &mut WidgetHostNative) {
|
|||
.chat
|
||||
.available_models
|
||||
.push(ModelEntry::new(
|
||||
AgentProvider::GeminiCli,
|
||||
"gemini-2.5-pro",
|
||||
"Gemini 2.5 Pro",
|
||||
AgentProvider::Antigravity,
|
||||
"default",
|
||||
"Antigravity Default",
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
//! `ChatProvider` → `LlmClient` adapter.
|
||||
//!
|
||||
//! Lets the orchestrator reuse whichever chat-panel agent the user
|
||||
//! already selected (Claude Code / Copilot / Gemini / …) as its LLM
|
||||
//! already selected (Claude Code / Copilot / Antigravity / …) as its LLM
|
||||
//! transport, instead of forcing a separate Anthropic API key.
|
||||
//!
|
||||
//! The CLI agents (`ClaudeCodeProvider`, `CopilotProvider`,
|
||||
//! `SubprocessProvider`) manage their own auth — `claude` is logged in
|
||||
//! by the user, Copilot rides GitHub auth, Gemini rides `gcloud`.
|
||||
//! by the user, Copilot rides GitHub auth, and Antigravity manages its
|
||||
//! Google account.
|
||||
//! `agent::Provider` (the `QueryEngine`-facing trait) is a different
|
||||
//! shape and only `AnthropicProvider` implements it, hence the original
|
||||
//! Anthropic-key requirement. This adapter eliminates that
|
||||
|
|
@ -232,9 +233,10 @@ mod tests {
|
|||
//
|
||||
// The manual per-subtask retry (failed-subtask remediation, phase 2)
|
||||
// reuses `ChatProviderLlmClient` with WHATEVER `ChatProvider` the user
|
||||
// currently has selected — a CLI subprocess agent (Claude Code /
|
||||
// Copilot / Gemini via `SubprocessProvider`) exactly as often as a
|
||||
// builtin API-key provider. `call()` above has no branch on the
|
||||
// currently has selected — a CLI-backed agent (Claude Code /
|
||||
// Copilot adapters, or Codex / Antigravity / Grok Build via
|
||||
// `SubprocessProvider`) exactly as often as a builtin API-key
|
||||
// provider. `call()` above has no branch on the
|
||||
// concrete provider type anywhere — it only calls the `ChatProvider`
|
||||
// trait method — so these two tests prove that structurally, with two
|
||||
// stub providers shaped like the two real families instead of relying
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Subprocess CLI bridge — spawns an external CLI binary
|
||||
//! (Claude Code / Codex / Gemini / Antigravity / Grok Build) and bridges its stdio into the
|
||||
//! (Claude Code / Codex / Antigravity / Grok Build) and bridges its stdio into the
|
||||
//! shell-core `ChatProvider` shape.
|
||||
//!
|
||||
//! Per-CLI wire protocol (single-shot mode; multi-turn context rides
|
||||
|
|
@ -22,13 +22,6 @@
|
|||
//! Divergence from TS (documented): TS buffers the whole turn and
|
||||
//! reads `--output-last-message`; Rust streams `item.completed`
|
||||
//! agent messages live — same final text, no temp file.
|
||||
//! - **Gemini CLI (`gemini`)** — `gemini -o stream-json
|
||||
//! --approval-mode plan [-m <id>] -p ' '` with the prompt piped via
|
||||
//! stdin (TS parity: `gemini-client.ts::streamGeminiExec`; the
|
||||
//! `-p ' '` marker is the TS quirk — Gemini appends the `-p` value
|
||||
//! after stdin content). Lines parse via
|
||||
//! [`chat_subprocess_quirks::parse_gemini_stream_line`]; child env
|
||||
//! is allowlist-filtered.
|
||||
//! - **GitHub Copilot** — no subprocess template. The routed path is
|
||||
//! the official SDK (`chat_copilot.rs`); the old `gh-copilot
|
||||
//! suggest` fallback was stale (the `gh` extension was retired in
|
||||
|
|
@ -51,13 +44,13 @@
|
|||
//! Its NDJSON `text`, `thought`, `end`, and `error` events parse through
|
||||
//! [`crate::chat_grok_stream::parse_grok_stream_line`].
|
||||
//!
|
||||
//! Codex / Gemini parse-misses are skipped silently (TS parity — TS
|
||||
//! Codex parse-misses are skipped silently (TS parity — TS
|
||||
//! drops unparsed lines); generic custom binaries degrade unparsed
|
||||
//! lines to raw `TextDelta` so plain-stdout CLIs still show output.
|
||||
//! On stdout EOF the bridge reaps the child + interprets exit status —
|
||||
//! non-zero exit surfaces as `Error + Done { Aborted }` (codex BLOCK
|
||||
//! 4 / 5), with Codex stderr routed through the TS error extractor.
|
||||
//! Codex / Gemini turns also carry the TS 15-minute wall clock
|
||||
//! Codex turns also carry the TS 15-minute wall clock
|
||||
//! (`<CLI> request timed out after 900s.`). On receiver drop the
|
||||
//! bridge `child.start_kill()` so the user can navigate away without
|
||||
//! leaking processes.
|
||||
|
|
@ -85,8 +78,8 @@ pub use crate::chat_subprocess_parse::parse_line;
|
|||
|
||||
/// How the user's prompt reaches the CLI. Claude Code's `--print`
|
||||
/// mode requires the prompt as a positional argv after `--` and
|
||||
/// closes stdin immediately. Codex (`-` prompt arg) and Gemini read
|
||||
/// the message off piped stdin. Generic `with_binary` callers can
|
||||
/// closes stdin immediately. Codex (`-` prompt arg) reads the
|
||||
/// message off piped stdin. Generic `with_binary` callers can
|
||||
/// pick either via [`SubprocessProvider::with_binary_mode`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PromptMode {
|
||||
|
|
@ -101,9 +94,9 @@ pub enum PromptMode {
|
|||
PromptFile(&'static str),
|
||||
}
|
||||
|
||||
/// TS `DEFAULT_CODEX_TIMEOUT_MS` / `DEFAULT_GEMINI_TIMEOUT_MS` —
|
||||
/// both reference clients cap a turn at 15 minutes then SIGTERM.
|
||||
const CLI_TURN_TIMEOUT: Duration = Duration::from_secs(15 * 60);
|
||||
/// TS `DEFAULT_CODEX_TIMEOUT_MS` — the reference client caps a turn
|
||||
/// at 15 minutes then SIGTERM.
|
||||
const CODEX_TURN_TIMEOUT: Duration = Duration::from_secs(15 * 60);
|
||||
|
||||
/// Cap on the retained Codex stderr tail used for error extraction.
|
||||
const STDERR_TAIL_CAP: usize = 64 * 1024;
|
||||
|
|
@ -117,8 +110,7 @@ pub struct SubprocessProvider {
|
|||
label: String,
|
||||
prompt_mode: PromptMode,
|
||||
/// Argv flag that selects a model for this CLI (`--model` for
|
||||
/// Codex, `-m` for Gemini — both verified against the TS
|
||||
/// reference clients). `None` = the transport has no model
|
||||
/// Codex and Antigravity, `-m` for Grok Build). `None` = the transport has no model
|
||||
/// selector; `ChatRequest::model` is ignored and the CLI keeps
|
||||
/// its own default.
|
||||
model_flag: Option<&'static str>,
|
||||
|
|
@ -129,8 +121,7 @@ pub struct SubprocessProvider {
|
|||
/// prose directive).
|
||||
native_effort_config: bool,
|
||||
/// Trailing argv appended after the per-turn flags — Codex's `-`
|
||||
/// stdin marker, Gemini's `-p ' '` marker. TS keeps the same
|
||||
/// flag-then-marker order (`codex-client.ts` / `gemini-client.ts`).
|
||||
/// stdin marker. TS keeps the same flag-then-marker order.
|
||||
tail_args: Vec<String>,
|
||||
/// Which known CLI this provider bridges (drives per-CLI env
|
||||
/// filtering, line parsing, stderr capture, and timeout quirks).
|
||||
|
|
@ -156,9 +147,8 @@ impl SubprocessProvider {
|
|||
|
||||
fn for_cli_with_purpose(cli: CliName, turn_purpose: safety::TurnPurpose) -> Option<Self> {
|
||||
// Per-CLI model selector (third tuple slot): Codex takes
|
||||
// `--model <id>` and Gemini `-m <id>` — matching the TS
|
||||
// reference (`codex-client.ts` / `gemini-client.ts`). Claude
|
||||
// Code's model rides the SDK adapter (`chat_claude.rs`), so
|
||||
// `--model <id>` — matching the TS reference. Claude Code's
|
||||
// model rides the SDK adapter (`chat_claude.rs`), so
|
||||
// its subprocess template carries no flag here.
|
||||
type Template = (Vec<String>, PromptMode, Option<&'static str>, Vec<String>);
|
||||
let (args, prompt_mode, model_flag, tail_args): Template = match cli {
|
||||
|
|
@ -173,20 +163,6 @@ impl SubprocessProvider {
|
|||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
// TS `gemini-client.ts::streamGeminiExec` argv order:
|
||||
// `-o stream-json --approval-mode plan [-m model] -p ' '`,
|
||||
// prompt via stdin.
|
||||
CliName::Gemini => (
|
||||
vec![
|
||||
"-o".into(),
|
||||
"stream-json".into(),
|
||||
"--approval-mode".into(),
|
||||
"plan".into(),
|
||||
],
|
||||
PromptMode::Stdin,
|
||||
Some("-m"),
|
||||
vec!["-p".into(), " ".into()],
|
||||
),
|
||||
// TS `codex-client.ts` argv: `exec --json
|
||||
// --skip-git-repo-check --sandbox read-only [--model]
|
||||
// [--config model_reasoning_effort=…] -` with the prompt
|
||||
|
|
@ -324,8 +300,8 @@ fn codex_reasoning_effort(thinking: ThinkingMode, effort: EffortLevel) -> Option
|
|||
/// can substitute a malicious binary; runtime-library paths
|
||||
/// (NODE_OPTIONS, PYTHONPATH, etc.) can inject code into Node-based
|
||||
/// CLIs. Mirrors bartolli/anthropic-agent-sdk's `DANGEROUS_ENV_VARS`.
|
||||
/// Used for Claude Code + custom binaries; Codex / Gemini get the
|
||||
/// stricter TS allowlists (`chat_subprocess_quirks`). Returns the
|
||||
/// Used for Claude Code + custom binaries; Codex gets the stricter
|
||||
/// TS allowlist (`chat_subprocess_quirks`). Returns the
|
||||
/// env-var pairs the child will receive (parent env minus the
|
||||
/// dangerous names — preserving every safe var so node version
|
||||
/// managers like nvm / volta still pick the right Node).
|
||||
|
|
@ -353,8 +329,8 @@ impl ChatProvider for SubprocessProvider {
|
|||
fn send(&self, request: ChatRequest) -> Box<dyn Iterator<Item = ChatDelta> + Send> {
|
||||
let binary = self.binary.clone();
|
||||
let cli = self.cli;
|
||||
// Build the effective prompt. Neither claude `--print` nor
|
||||
// gemini exposes a portable reasoning flag, so the thinking +
|
||||
// Build the effective prompt. Claude `--print` does not
|
||||
// expose a portable reasoning flag, so the thinking +
|
||||
// effort knobs travel in-band as a leading directive line
|
||||
// (documented beyond-TS-baseline behavior); staged attachments
|
||||
// are appended as `[attached …: <path>]` lines the CLI can
|
||||
|
|
@ -400,13 +376,11 @@ impl ChatProvider for SubprocessProvider {
|
|||
if !digest.is_empty() {
|
||||
prompt = format!("{digest}\n\n{prompt}");
|
||||
}
|
||||
// System-prompt framing: Codex + Gemini mirror the TS
|
||||
// GUIDELINES / TASK `buildPrompt` byte-for-byte; everything
|
||||
// System-prompt framing: Codex mirrors the TS GUIDELINES /
|
||||
// TASK `buildPrompt` byte-for-byte; everything
|
||||
// else keeps the generic `system\n\n---\n\nprompt` join.
|
||||
prompt = match cli {
|
||||
Some(CliName::Codex) | Some(CliName::Gemini) => {
|
||||
quirks::guidelines_task_prompt(&request.system_prompt, &prompt)
|
||||
}
|
||||
Some(CliName::Codex) => quirks::guidelines_task_prompt(&request.system_prompt, &prompt),
|
||||
_ => prompt_with_system_prompt(&request.system_prompt, prompt),
|
||||
};
|
||||
let attachment_paths = guard.as_ref().map(|g| g.paths()).unwrap_or(&[]);
|
||||
|
|
@ -467,12 +441,11 @@ impl ChatProvider for SubprocessProvider {
|
|||
}
|
||||
let args = Arc::new(args_with_prompt);
|
||||
let prompt_mode = self.prompt_mode;
|
||||
// Per-CLI child env: Codex / Gemini use the TS allowlists so
|
||||
// Per-CLI child env: Codex uses the TS allowlist so
|
||||
// unrelated secrets never reach the CLI; Claude Code + custom
|
||||
// binaries keep the scrub-the-dangerous-vars policy.
|
||||
let mut env_pairs = match cli {
|
||||
Some(CliName::Codex) => quirks::codex_child_env(),
|
||||
Some(CliName::Gemini) => quirks::gemini_child_env(),
|
||||
Some(CliName::Antigravity | CliName::GrokBuild) => {
|
||||
safety::child_env(cli).unwrap_or_default()
|
||||
}
|
||||
|
|
@ -480,7 +453,7 @@ impl ChatProvider for SubprocessProvider {
|
|||
};
|
||||
safety::append_isolated_env(&mut env_pairs, isolation.as_ref());
|
||||
let turn_timeout = match cli {
|
||||
Some(CliName::Codex | CliName::Gemini) => Some(CLI_TURN_TIMEOUT),
|
||||
Some(CliName::Codex) => Some(CODEX_TURN_TIMEOUT),
|
||||
Some(CliName::Antigravity) => Some(safety::ANTIGRAVITY_TIMEOUT),
|
||||
Some(CliName::GrokBuild) => Some(safety::GROK_TIMEOUT),
|
||||
_ => None,
|
||||
|
|
@ -518,8 +491,7 @@ impl ChatProvider for SubprocessProvider {
|
|||
// Drain stderr on a sibling task so the CLI never
|
||||
// deadlocks on a full stderr pipe (codex BLOCK 1). Codex
|
||||
// keeps a bounded tail for the TS error extraction
|
||||
// (`extractCodexCliError`); other CLIs discard it (TS
|
||||
// parity: gemini stream path discards stderr).
|
||||
// (`extractCodexCliError`); other CLIs discard it.
|
||||
let stderr_tail: Arc<std::sync::Mutex<String>> = Arc::default();
|
||||
if let Some(stderr) = child.take_stderr() {
|
||||
let capture = (cli == Some(CliName::Codex) || safety::is_guarded_cli(cli))
|
||||
|
|
@ -607,7 +579,7 @@ impl ChatProvider for SubprocessProvider {
|
|||
break;
|
||||
}
|
||||
// TS wall clock: SIGTERM + "request timed out"
|
||||
// (codex-client.ts / gemini-client.ts reject text).
|
||||
// (`codex-client.ts` rejects text).
|
||||
_ = tokio::time::sleep_until(deadline), if turn_timeout.is_some() => {
|
||||
let secs = turn_timeout.map(|d| d.as_secs()).unwrap_or_default();
|
||||
let _ = tx
|
||||
|
|
@ -633,12 +605,11 @@ impl ChatProvider for SubprocessProvider {
|
|||
let _ = child.start_kill();
|
||||
break;
|
||||
}
|
||||
// Per-CLI parse: Codex / Gemini skip
|
||||
// unparsed lines (TS parity); generic
|
||||
// Per-CLI parse: Codex skips unparsed
|
||||
// lines (TS parity); generic
|
||||
// CLIs degrade them to raw text.
|
||||
let delta = match cli {
|
||||
Some(CliName::Codex) => quirks::parse_codex_line(&line),
|
||||
Some(CliName::Gemini) => quirks::parse_gemini_stream_line(&line),
|
||||
Some(CliName::GrokBuild) => {
|
||||
crate::chat_grok_stream::parse_grok_stream_line(&line)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
//! Per-CLI quirks for the Codex / Gemini subprocess transports —
|
||||
//! verbatim ports of the TS reference clients:
|
||||
//! Per-CLI quirks for the Codex subprocess transport — a verbatim
|
||||
//! port of the TS reference client:
|
||||
//!
|
||||
//! - `apps/web/server/utils/codex-client.ts` — env allowlist (+
|
||||
//! `~/.codex/config.toml` `env_key` opt-ins), stderr error
|
||||
//! extraction with auth-hint rewrites, JSONL line parsing.
|
||||
//! - `apps/web/server/utils/gemini-client.ts` — env allowlist,
|
||||
//! `-o stream-json` line parsing, user-friendly API error mapping.
|
||||
//!
|
||||
//! Both also share the TS `buildPrompt` GUIDELINES / TASK framing so
|
||||
//! It also keeps the TS `buildPrompt` GUIDELINES / TASK framing so
|
||||
//! the wire prompt matches the TS server byte-for-byte.
|
||||
|
||||
use op_ai::chat_provider::{ChatDelta, StopReason};
|
||||
|
|
@ -50,11 +48,6 @@ const CODEX_ENV_ALLOWLIST: &[&str] = &[
|
|||
"HOMEPATH",
|
||||
];
|
||||
|
||||
/// Allowlist-based env filter for the Gemini CLI subprocess (TS
|
||||
/// `gemini-client.ts::GEMINI_ENV_ALLOWLIST` — same system core, no
|
||||
/// extra config-file opt-ins).
|
||||
const GEMINI_ENV_ALLOWLIST: &[&str] = CODEX_ENV_ALLOWLIST;
|
||||
|
||||
/// Extract provider-declared `env_key` entries from
|
||||
/// `~/.codex/config.toml` content. Preserves the default safety
|
||||
/// boundary of not forwarding sensitive environment variables
|
||||
|
|
@ -120,22 +113,9 @@ pub fn codex_child_env() -> Vec<(String, String)> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Gemini child env: allowlisted system vars + `GOOGLE_*` /
|
||||
/// `GEMINI_*` / `GCLOUD_*` prefixes (TS `filterGeminiEnv`).
|
||||
pub fn gemini_child_env() -> Vec<(String, String)> {
|
||||
std::env::vars()
|
||||
.filter(|(k, _)| {
|
||||
GEMINI_ENV_ALLOWLIST.contains(&k.as_str())
|
||||
|| k.starts_with("GOOGLE_")
|
||||
|| k.starts_with("GEMINI_")
|
||||
|| k.starts_with("GCLOUD_")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// TS `codex-client.ts::buildPrompt` / `gemini-client.ts::buildPrompt`
|
||||
/// framing: an empty system prompt passes the task through; otherwise
|
||||
/// the system prompt rides a GUIDELINES block ahead of the TASK block.
|
||||
/// TS `codex-client.ts::buildPrompt` framing: an empty system prompt
|
||||
/// passes the task through; otherwise the system prompt rides a
|
||||
/// GUIDELINES block ahead of the TASK block.
|
||||
pub fn guidelines_task_prompt(system_prompt: &str, task: &str) -> String {
|
||||
let task = task.trim();
|
||||
let system = system_prompt.trim();
|
||||
|
|
@ -320,88 +300,6 @@ fn is_codex_auth_error(msg: &str) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
/// Parse one `gemini -o stream-json` stdout line (TS
|
||||
/// `parseStreamJsonLine`). `None` = skip silently — non-JSON noise
|
||||
/// like "Loaded cached credentials." never reaches the transcript.
|
||||
pub fn parse_gemini_stream_line(line: &str) -> Option<ChatDelta> {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.starts_with('{') {
|
||||
return None;
|
||||
}
|
||||
let val: serde_json::Value = serde_json::from_str(trimmed).ok()?;
|
||||
let ty = val.get("type").and_then(|v| v.as_str()).unwrap_or("");
|
||||
|
||||
if ty == "message" && val.get("role").and_then(|v| v.as_str()) == Some("assistant") {
|
||||
let content = val.get("content").and_then(|v| v.as_str()).unwrap_or("");
|
||||
if !content.is_empty() {
|
||||
return Some(ChatDelta::TextDelta(content.to_string()));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if ty == "result" {
|
||||
// Check for error in result event; a success result carries
|
||||
// no renderable delta (the text already streamed).
|
||||
if val.get("status").and_then(|v| v.as_str()) == Some("error") {
|
||||
if let Some(err) = val.get("error") {
|
||||
let msg = err
|
||||
.get("message")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Unknown error");
|
||||
return Some(ChatDelta::Error(friendly_gemini_api_error(msg)));
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if ty == "error" {
|
||||
let msg = val
|
||||
.get("message")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Unknown error");
|
||||
return Some(ChatDelta::Error(friendly_gemini_api_error(msg)));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Map raw Gemini API errors to user-friendly messages (TS
|
||||
/// `friendlyGeminiApiError`).
|
||||
pub fn friendly_gemini_api_error(raw: &str) -> String {
|
||||
let lower = raw.to_ascii_lowercase();
|
||||
if lower.contains("quota")
|
||||
|| lower.contains("exhausted")
|
||||
|| lower.contains("429")
|
||||
|| lower.contains("capacity")
|
||||
{
|
||||
if let Some(reset) = match_reset_after(raw) {
|
||||
return format!("Gemini quota exhausted. Resets after {reset}.");
|
||||
}
|
||||
return "Gemini quota exhausted. Please wait and try again.".to_string();
|
||||
}
|
||||
if lower.contains("401") || lower.contains("unauthenticated") || lower.contains("auth") {
|
||||
return "Gemini auth expired. Run \"gemini\" in your terminal to re-authenticate."
|
||||
.to_string();
|
||||
}
|
||||
if raw.contains("[object Object]") {
|
||||
return "Gemini API error. Check your quota or try a different model.".to_string();
|
||||
}
|
||||
raw.to_string()
|
||||
}
|
||||
|
||||
/// TS regex `/reset after (\S+)/i` — the quota-reset timestamp token.
|
||||
fn match_reset_after(raw: &str) -> Option<&str> {
|
||||
let lower = raw.to_ascii_lowercase();
|
||||
let pos = lower.find("reset after ")?;
|
||||
let tail = &raw[pos + "reset after ".len()..];
|
||||
let token: &str = tail.split_whitespace().next()?;
|
||||
if token.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(token)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -529,60 +427,4 @@ env_key = not_quoted
|
|||
Some("something odd happened")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_line_parser_assistant_messages_and_noise() {
|
||||
assert_eq!(parse_gemini_stream_line("Loaded cached credentials."), None);
|
||||
assert_eq!(
|
||||
parse_gemini_stream_line(r#"{"type":"message","role":"assistant","content":"Hello"}"#),
|
||||
Some(ChatDelta::TextDelta("Hello".into()))
|
||||
);
|
||||
// Empty content / non-assistant roles are skipped.
|
||||
assert_eq!(
|
||||
parse_gemini_stream_line(r#"{"type":"message","role":"assistant","content":""}"#),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
parse_gemini_stream_line(r#"{"type":"message","role":"user","content":"x"}"#),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_line_parser_result_and_error_events() {
|
||||
// Success result carries no delta.
|
||||
assert_eq!(
|
||||
parse_gemini_stream_line(r#"{"type":"result","status":"ok","response":"done"}"#),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
parse_gemini_stream_line(
|
||||
r#"{"type":"result","status":"error","error":{"message":"401 unauthenticated"}}"#
|
||||
),
|
||||
Some(ChatDelta::Error(
|
||||
"Gemini auth expired. Run \"gemini\" in your terminal to re-authenticate.".into()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_gemini_stream_line(r#"{"type":"error","message":"plain failure"}"#),
|
||||
Some(ChatDelta::Error("plain failure".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_friendly_error_table_matches_ts() {
|
||||
assert_eq!(
|
||||
friendly_gemini_api_error("Quota will reset after 3:00PM details"),
|
||||
"Gemini quota exhausted. Resets after 3:00PM."
|
||||
);
|
||||
assert_eq!(
|
||||
friendly_gemini_api_error("429 too many requests"),
|
||||
"Gemini quota exhausted. Please wait and try again."
|
||||
);
|
||||
assert_eq!(
|
||||
friendly_gemini_api_error("[object Object]"),
|
||||
"Gemini API error. Check your quota or try a different model."
|
||||
);
|
||||
assert_eq!(friendly_gemini_api_error("boring"), "boring");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,26 +124,6 @@ fn for_cli_claude_code_seeds_print_stream_json_flags() {
|
|||
assert_eq!(p.prompt_mode, PromptMode::PositionalArg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_cli_uses_default_binary_per_cli_name() {
|
||||
// Resolved path may be bare or absolute depending on what's
|
||||
// installed on the test host; check the basename only.
|
||||
let gemini = SubprocessProvider::for_cli(CliName::Gemini).unwrap();
|
||||
assert!(
|
||||
gemini.binary.ends_with("gemini"),
|
||||
"binary={}",
|
||||
gemini.binary
|
||||
);
|
||||
assert_eq!(gemini.prompt_mode, PromptMode::Stdin);
|
||||
// TS gemini-client.ts streamGeminiExec argv: `-o stream-json
|
||||
// --approval-mode plan … -p ' '` (prompt via stdin).
|
||||
assert_eq!(
|
||||
gemini.args,
|
||||
vec!["-o", "stream-json", "--approval-mode", "plan"]
|
||||
);
|
||||
assert_eq!(gemini.tail_args, vec!["-p", " "]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_cli_rejects_opencode_and_copilot() {
|
||||
// OpenCode chats over its HTTP server (chat_http_server.rs);
|
||||
|
|
@ -299,29 +279,6 @@ fn codex_turn_args_map_effort_to_reasoning_config() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_turn_args_append_m_flag_when_selected() {
|
||||
let p = SubprocessProvider::for_cli(CliName::Gemini).unwrap();
|
||||
let args = p.turn_args(&request_with_model(Some("gemini-2.5-pro")));
|
||||
let pos = args
|
||||
.iter()
|
||||
.position(|a| a == "-m")
|
||||
.expect("-m flag present");
|
||||
assert_eq!(args[pos + 1], "gemini-2.5-pro");
|
||||
// Gemini has no native effort knob — never a --config arg.
|
||||
assert!(!args.iter().any(|a| a == "--config"));
|
||||
// The `-p ' '` stdin marker trails the model flag (TS order).
|
||||
assert_eq!(args[args.len() - 2], "-p");
|
||||
assert_eq!(args[args.len() - 1], " ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_turn_args_omit_m_flag_when_unset() {
|
||||
let p = SubprocessProvider::for_cli(CliName::Gemini).unwrap();
|
||||
let args = p.turn_args(&request_with_model(None));
|
||||
assert!(!args.iter().any(|a| a == "-m"), "args={args:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_subprocess_template_ignores_model() {
|
||||
// Claude Code's model rides the SDK adapter (chat_claude.rs); its
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ pub fn model_entry_to_ec(m: ModelEntry) -> op_editor_core::ModelEntry {
|
|||
ScP::CodexCli => op_editor_core::AgentProvider::CodexCli,
|
||||
ScP::OpenCode => op_editor_core::AgentProvider::OpenCode,
|
||||
ScP::GithubCopilot => op_editor_core::AgentProvider::GithubCopilot,
|
||||
ScP::GeminiCli => op_editor_core::AgentProvider::GeminiCli,
|
||||
ScP::Antigravity => op_editor_core::AgentProvider::Antigravity,
|
||||
ScP::GrokBuild => op_editor_core::AgentProvider::GrokBuild,
|
||||
};
|
||||
|
|
@ -47,12 +46,12 @@ pub fn model_entry_to_ec(m: ModelEntry) -> op_editor_core::ModelEntry {
|
|||
/// provider-grouped model list. Safe to call off the UI thread —
|
||||
/// it only reads files and spawns short-lived subprocesses.
|
||||
pub fn discover_models() -> Vec<ModelEntry> {
|
||||
discover_models_for_connected([true; 7])
|
||||
discover_models_for_connected([true; 6])
|
||||
}
|
||||
|
||||
/// Discover a startup-selected provider set concurrently while preserving
|
||||
/// the stable Settings/model-picker provider order in the returned catalog.
|
||||
pub fn discover_models_for_connected(connected: [bool; 7]) -> Vec<ModelEntry> {
|
||||
pub fn discover_models_for_connected(connected: [bool; 6]) -> Vec<ModelEntry> {
|
||||
let workers: Vec<_> = discovery_provider_order()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
|
|
@ -71,7 +70,6 @@ fn discover_provider(provider: AgentProvider) -> Vec<ModelEntry> {
|
|||
AgentProvider::CodexCli => discover_codex(),
|
||||
AgentProvider::OpenCode => discover_opencode(),
|
||||
AgentProvider::GithubCopilot => discover_copilot(),
|
||||
AgentProvider::GeminiCli => discover_gemini(),
|
||||
AgentProvider::Antigravity => crate::cli_model_discovery::discover_antigravity(),
|
||||
AgentProvider::GrokBuild => crate::cli_model_discovery::discover_grok(),
|
||||
}
|
||||
|
|
@ -79,7 +77,7 @@ fn discover_provider(provider: AgentProvider) -> Vec<ModelEntry> {
|
|||
|
||||
/// Provider probe order mirrors TS `DEFAULT_PROVIDERS`, which is
|
||||
/// also the core `AgentProvider::ALL` order used by Settings.
|
||||
fn discovery_provider_order() -> [AgentProvider; 7] {
|
||||
fn discovery_provider_order() -> [AgentProvider; 6] {
|
||||
AgentProvider::ALL
|
||||
}
|
||||
|
||||
|
|
@ -344,23 +342,6 @@ pub fn parse_codex_models_cache(raw: &str) -> Option<Vec<ModelEntry>> {
|
|||
Some(keyed.into_iter().map(|(_, model)| model).collect())
|
||||
}
|
||||
|
||||
/// Gemini CLI — live model listing from the generativelanguage API
|
||||
/// using the CLI's own credentials (API key env or OAuth token,
|
||||
/// connect-agent.ts:904-973), falling back to the TS
|
||||
/// `FALLBACK_GEMINI_MODELS` list when the fetch fails or returns
|
||||
/// nothing — gated on the CLI being installed.
|
||||
fn discover_gemini() -> Vec<ModelEntry> {
|
||||
if resolve_cli("gemini").is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
if let Some(models) = crate::provider_probe_models::gemini_models_from_api() {
|
||||
if !models.is_empty() {
|
||||
return models;
|
||||
}
|
||||
}
|
||||
crate::provider_probe_models::fallback_gemini_models()
|
||||
}
|
||||
|
||||
/// How long to wait for `copilot --stdio` to answer `models.list`.
|
||||
const COPILOT_STDIO_TIMEOUT: Duration = Duration::from_secs(8);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,26 +8,26 @@ use crate::model_discovery::{discover_models_for_connected, model_entry_to_ec};
|
|||
|
||||
pub struct ModelProbe {
|
||||
rx: Option<Receiver<Vec<ModelEntry>>>,
|
||||
providers: [bool; 7],
|
||||
providers: [bool; 6],
|
||||
}
|
||||
|
||||
impl ModelProbe {
|
||||
pub fn idle() -> Self {
|
||||
Self {
|
||||
rx: None,
|
||||
providers: [false; 7],
|
||||
providers: [false; 6],
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a full-catalog discovery worker for compatibility with callers
|
||||
/// that explicitly need every locally installed provider.
|
||||
pub fn spawn() -> Self {
|
||||
Self::spawn_for_connected([true; 7])
|
||||
Self::spawn_for_connected([true; 6])
|
||||
}
|
||||
|
||||
/// Probe only providers connected at startup. Provider queries execute
|
||||
/// concurrently, so their individual timeouts do not stack serially.
|
||||
pub fn spawn_for_connected(connected: [bool; 7]) -> Self {
|
||||
pub fn spawn_for_connected(connected: [bool; 6]) -> Self {
|
||||
if !connected.iter().any(|is_connected| *is_connected) {
|
||||
return Self::idle();
|
||||
}
|
||||
|
|
@ -47,12 +47,12 @@ impl ModelProbe {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn pending_for_test() -> (Self, mpsc::Sender<Vec<ModelEntry>>) {
|
||||
Self::pending_for_providers_for_test([true; 7])
|
||||
Self::pending_for_providers_for_test([true; 6])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn pending_for_providers_for_test(
|
||||
providers: [bool; 7],
|
||||
providers: [bool; 6],
|
||||
) -> (Self, mpsc::Sender<Vec<ModelEntry>>) {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
(
|
||||
|
|
@ -111,13 +111,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn empty_connected_mask_stays_idle() {
|
||||
assert!(!ModelProbe::spawn_for_connected([false; 7]).is_pending());
|
||||
assert!(!ModelProbe::spawn_for_connected([false; 6]).is_pending());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filtered_result_does_not_erase_unprobed_provider() {
|
||||
let mut providers = [false; 7];
|
||||
providers[5] = true;
|
||||
let mut providers = [false; 6];
|
||||
providers[4] = true;
|
||||
let (mut probe, tx) = ModelProbe::pending_for_providers_for_test(providers);
|
||||
let mut state = EditorState::default();
|
||||
state.chat.discovered_models.push(EditorModelEntry::new(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
//!
|
||||
//! Pressing Connect on a provider card runs a real probe: binary
|
||||
//! resolution (installed?), a responsiveness gate where TS runs one
|
||||
//! (`codex --version` / `gemini --version`), a live model query, and
|
||||
//! (`codex --version`), a live model query, and
|
||||
//! an auth-state read that yields the human-readable
|
||||
//! `connectionInfo` string ("Connected via pro (a@b.c)"). Failures
|
||||
//! map through the same friendly-error tables the TS route uses.
|
||||
|
|
@ -27,8 +27,8 @@ use crate::model_discovery::{
|
|||
parse_copilot_model_list, resolve_cli, write_lsp_frame,
|
||||
};
|
||||
use crate::provider_probe_models::{
|
||||
claude_initialize_query, codex_home, codex_models_from_latest_md, gemini_models_from_api,
|
||||
ClaudeAccount, ClaudeInitResult,
|
||||
claude_initialize_query, codex_home, codex_models_from_latest_md, ClaudeAccount,
|
||||
ClaudeInitResult,
|
||||
};
|
||||
|
||||
/// Probe result — the TS `ConnectResult` shape plus the install
|
||||
|
|
@ -78,9 +78,6 @@ fn no_models_error(provider: AgentProvider) -> String {
|
|||
AgentProvider::GithubCopilot => {
|
||||
"No models found. Run \"copilot login\" to authenticate first.".to_string()
|
||||
}
|
||||
AgentProvider::GeminiCli => {
|
||||
"No models found. Run \"gemini\" once to authenticate, or set GEMINI_API_KEY.".to_string()
|
||||
}
|
||||
AgentProvider::Antigravity => {
|
||||
"No model available. Run \"agy\" once to authenticate.".to_string()
|
||||
}
|
||||
|
|
@ -119,7 +116,6 @@ pub fn connect_provider(provider: AgentProvider) -> ProbeOutcome {
|
|||
AgentProvider::CodexCli => connect_codex_cli(),
|
||||
AgentProvider::OpenCode => connect_opencode(),
|
||||
AgentProvider::GithubCopilot => connect_copilot(),
|
||||
AgentProvider::GeminiCli => connect_gemini_cli(),
|
||||
AgentProvider::Antigravity => crate::cli_provider_probe::connect_antigravity(),
|
||||
AgentProvider::GrokBuild => crate::cli_provider_probe::connect_grok_build(),
|
||||
}
|
||||
|
|
@ -157,7 +153,6 @@ fn install_command_for_platform(
|
|||
"See documentation"
|
||||
}
|
||||
}
|
||||
AgentProvider::GeminiCli => "npm install -g @anthropic-ai/gemini-cli",
|
||||
AgentProvider::Antigravity if windows => {
|
||||
"irm https://antigravity.google/cli/install.ps1 | iex"
|
||||
}
|
||||
|
|
@ -704,73 +699,6 @@ fn friendly_copilot_error(raw: &str) -> String {
|
|||
raw.to_string()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Gemini CLI (connect-agent.ts:975-1063)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
fn connect_gemini_cli() -> ProbeOutcome {
|
||||
let Some(exe) = resolve_cli("gemini") else {
|
||||
return ProbeOutcome::not_installed(AgentProvider::GeminiCli, "Gemini CLI not found");
|
||||
};
|
||||
// Responsiveness gate — TS runs `gemini --version` with a 10 s
|
||||
// budget (connect-agent.ts:986-997).
|
||||
let Some(version) = cli_version(&exe, Duration::from_secs(10)) else {
|
||||
return ProbeOutcome::failed("Gemini CLI not responding".to_string());
|
||||
};
|
||||
let models = match gemini_models_from_api() {
|
||||
Some(models) if !models.is_empty() => models,
|
||||
Some(_) => return ProbeOutcome::failed(no_models_error(AgentProvider::GeminiCli)),
|
||||
None => {
|
||||
return ProbeOutcome::failed(
|
||||
"Unable to fetch Gemini models. Run \"gemini\" once to authenticate, or set GEMINI_API_KEY.".to_string(),
|
||||
)
|
||||
}
|
||||
};
|
||||
let (info, hint) = build_gemini_connection_info();
|
||||
connected_probe_outcome(
|
||||
AgentProvider::GeminiCli,
|
||||
models,
|
||||
Some(info),
|
||||
None,
|
||||
Some(hint),
|
||||
Some(version),
|
||||
)
|
||||
}
|
||||
|
||||
/// TS `buildGeminiConnectionInfo` (connect-agent.ts:1027-1063).
|
||||
fn build_gemini_connection_info() -> (String, String) {
|
||||
let hint = config_path(
|
||||
"~/.gemini/settings.json",
|
||||
"%USERPROFILE%\\.gemini\\settings.json",
|
||||
);
|
||||
let env_key = std::env::var("GEMINI_API_KEY")
|
||||
.ok()
|
||||
.filter(|k| !k.is_empty())
|
||||
.or_else(|| {
|
||||
std::env::var("GOOGLE_API_KEY")
|
||||
.ok()
|
||||
.filter(|k| !k.is_empty())
|
||||
});
|
||||
if let Some(key) = env_key {
|
||||
return (format!("Connected via API key ({})", mask_key(&key)), hint);
|
||||
}
|
||||
let gemini_dir = dirs::home_dir().map(|h| h.join(".gemini"));
|
||||
if let Some(dir) = gemini_dir {
|
||||
if dir.join("oauth_creds.json").is_file() {
|
||||
// Try the active Google account email.
|
||||
if let Ok(raw) = std::fs::read_to_string(dir.join("google_accounts.json")) {
|
||||
if let Ok(accounts) = serde_json::from_str::<serde_json::Value>(&raw) {
|
||||
if let Some(active) = accounts.get("active").and_then(|v| v.as_str()) {
|
||||
return (format!("Connected via Google ({active})"), hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ("Connected via Google OAuth".to_string(), hint);
|
||||
}
|
||||
}
|
||||
("Connected via Gemini CLI".to_string(), hint)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "provider_probe_tests.rs"]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ fn provider_to_sc(p: op_editor_core::AgentProvider) -> op_ai::agent_settings_sta
|
|||
op_editor_core::AgentProvider::CodexCli => Sc::CodexCli,
|
||||
op_editor_core::AgentProvider::OpenCode => Sc::OpenCode,
|
||||
op_editor_core::AgentProvider::GithubCopilot => Sc::GithubCopilot,
|
||||
op_editor_core::AgentProvider::GeminiCli => Sc::GeminiCli,
|
||||
op_editor_core::AgentProvider::Antigravity => Sc::Antigravity,
|
||||
op_editor_core::AgentProvider::GrokBuild => Sc::GrokBuild,
|
||||
}
|
||||
|
|
@ -116,9 +115,6 @@ pub fn missing_models_connect_error(provider: op_editor_core::AgentProvider) ->
|
|||
op_editor_core::AgentProvider::GithubCopilot => {
|
||||
"No models found. GitHub Copilot did not return a model list.".to_string()
|
||||
}
|
||||
op_editor_core::AgentProvider::GeminiCli => {
|
||||
"No models found. Gemini CLI did not return a model list.".to_string()
|
||||
}
|
||||
op_editor_core::AgentProvider::Antigravity => {
|
||||
"No model available. Antigravity did not expose its default model.".to_string()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Live model-list queries for Claude + Gemini, plus the Codex
|
||||
//! Live model-list queries for Claude, plus the Codex
|
||||
//! `latest-model.md` fallback parser.
|
||||
//!
|
||||
//! These are the "no hardcoded lists" upgrades shared by startup
|
||||
|
|
@ -14,11 +14,6 @@
|
|||
//! --verbose --input-format stream-json …`, write one
|
||||
//! `control_request {subtype:"initialize"}` line, read the
|
||||
//! `control_response` carrying `models` + `account`.
|
||||
//! - Gemini: the `generativelanguage.googleapis.com/v1beta/models`
|
||||
//! listing authenticated by `GEMINI_API_KEY` / `GOOGLE_API_KEY`
|
||||
//! or the CLI's `~/.gemini/oauth_creds.json` access token
|
||||
//! (connect-agent.ts:904-973), filtered to `generateContent`
|
||||
//! models and sorted gemini-3 first.
|
||||
//! - Codex: `~/.codex/skills/.system/openai-docs/references/
|
||||
//! latest-model.md` markdown-table rows when `models_cache.json`
|
||||
//! is missing (connect-agent.ts:378-413).
|
||||
|
|
@ -215,156 +210,6 @@ pub fn fallback_claude_models() -> Vec<ModelEntry> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// TS `FALLBACK_GEMINI_MODELS` (connect-agent.ts:870-901).
|
||||
pub fn fallback_gemini_models() -> Vec<ModelEntry> {
|
||||
[
|
||||
("gemini-3-pro-preview", "Gemini 3 Pro"),
|
||||
("gemini-3-flash-preview", "Gemini 3 Flash"),
|
||||
("gemini-2.5-pro", "Gemini 2.5 Pro"),
|
||||
("gemini-2.5-flash", "Gemini 2.5 Flash"),
|
||||
("gemini-2.0-flash", "Gemini 2.0 Flash"),
|
||||
]
|
||||
.iter()
|
||||
.map(|(value, name)| ModelEntry::new(AgentProvider::GeminiCli, *value, *name))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Fetch the Gemini model list from the generativelanguage API
|
||||
/// using the same local credentials the TS route reads
|
||||
/// (connect-agent.ts:904-973). `None` = no usable credentials or
|
||||
/// the request failed (callers fall back silently, like the TS
|
||||
/// `catch`); `Some(vec![])` = the API answered with nothing usable
|
||||
/// (TS surfaces a warning).
|
||||
pub fn gemini_models_from_api() -> Option<Vec<ModelEntry>> {
|
||||
const BASE: &str = "https://generativelanguage.googleapis.com/v1beta/models";
|
||||
let env_key = std::env::var("GEMINI_API_KEY")
|
||||
.ok()
|
||||
.filter(|k| !k.is_empty())
|
||||
.or_else(|| {
|
||||
std::env::var("GOOGLE_API_KEY")
|
||||
.ok()
|
||||
.filter(|k| !k.is_empty())
|
||||
});
|
||||
let (url, bearer) = match env_key {
|
||||
Some(key) => (format!("{BASE}?key={key}"), None),
|
||||
None => {
|
||||
// OAuth token from the Gemini CLI login, with the same
|
||||
// 60 s expiry guard the TS route applies.
|
||||
let creds_path = dirs::home_dir()?.join(".gemini").join("oauth_creds.json");
|
||||
let raw = std::fs::read_to_string(creds_path).ok()?;
|
||||
let creds: serde_json::Value = serde_json::from_str(&raw).ok()?;
|
||||
let token = creds.get("access_token")?.as_str()?.to_string();
|
||||
if let Some(expiry) = creds.get("expiry_date").and_then(|v| v.as_f64()) {
|
||||
let now_ms = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.ok()?
|
||||
.as_millis() as f64;
|
||||
if now_ms > expiry - 60_000.0 {
|
||||
return None; // Token expired.
|
||||
}
|
||||
}
|
||||
(BASE.to_string(), Some(token))
|
||||
}
|
||||
};
|
||||
|
||||
let body = http_get_json(&url, bearer.as_deref())?;
|
||||
Some(parse_gemini_models_json(&body))
|
||||
}
|
||||
|
||||
/// Blocking GET returning the response body. reqwest is async-only
|
||||
/// in this workspace, so spin a current-thread runtime — the same
|
||||
/// shape `iconify_host.rs` uses.
|
||||
fn http_get_json(url: &str, bearer: Option<&str>) -> Option<String> {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.ok()?;
|
||||
runtime.block_on(async {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(15))
|
||||
.user_agent(concat!("openpencil-desktop/", env!("CARGO_PKG_VERSION")))
|
||||
.build()
|
||||
.ok()?;
|
||||
let mut req = client.get(url);
|
||||
if let Some(token) = bearer {
|
||||
req = req.header("Authorization", format!("Bearer {token}"));
|
||||
}
|
||||
let res = req.send().await.ok()?.error_for_status().ok()?;
|
||||
res.text().await.ok()
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse + filter + sort the generativelanguage models listing —
|
||||
/// `generateContent` models only, embedding/AQA/legacy ids skipped,
|
||||
/// gemini-3 sorted first (connect-agent.ts:933-972).
|
||||
pub fn parse_gemini_models_json(body: &str) -> Vec<ModelEntry> {
|
||||
let Ok(json) = serde_json::from_str::<serde_json::Value>(body) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(arr) = json.get("models").and_then(|v| v.as_array()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut models = Vec::new();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for m in arr {
|
||||
let supports_generate = m
|
||||
.get("supportedGenerationMethods")
|
||||
.and_then(|v| v.as_array())
|
||||
.is_some_and(|methods| {
|
||||
methods
|
||||
.iter()
|
||||
.any(|v| v.as_str() == Some("generateContent"))
|
||||
});
|
||||
if !supports_generate {
|
||||
continue;
|
||||
}
|
||||
let id = m
|
||||
.get("name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.trim_start_matches("models/")
|
||||
.to_string();
|
||||
if id.is_empty() || seen.contains(&id) || gemini_id_is_skipped(&id) {
|
||||
continue;
|
||||
}
|
||||
seen.insert(id.clone());
|
||||
let display = m
|
||||
.get("displayName")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or(&id)
|
||||
.to_string();
|
||||
models.push(ModelEntry::new(AgentProvider::GeminiCli, id, display));
|
||||
}
|
||||
// Stable sort keeps the API order within each tier.
|
||||
models.sort_by_key(|m| gemini_tier(&m.value));
|
||||
models
|
||||
}
|
||||
|
||||
/// TS skip regex `/embed|aqa|^chat-bison|^text-bison|^gemini-1\.0/i`.
|
||||
fn gemini_id_is_skipped(id: &str) -> bool {
|
||||
let lower = id.to_ascii_lowercase();
|
||||
lower.contains("embed")
|
||||
|| lower.contains("aqa")
|
||||
|| lower.starts_with("chat-bison")
|
||||
|| lower.starts_with("text-bison")
|
||||
|| lower.starts_with("gemini-1.0")
|
||||
}
|
||||
|
||||
/// TS sort order fn (connect-agent.ts:961-969).
|
||||
fn gemini_tier(value: &str) -> u8 {
|
||||
if value.contains("gemini-3") {
|
||||
0
|
||||
} else if value.contains("gemini-2.5-pro") {
|
||||
1
|
||||
} else if value.contains("gemini-2.5-flash") {
|
||||
2
|
||||
} else if value.contains("gemini-2.0") {
|
||||
3
|
||||
} else {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
/// `CODEX_HOME` env override or `~/.codex` — the same resolution
|
||||
/// the TS route applies (connect-agent.ts:526).
|
||||
pub fn codex_home() -> Option<PathBuf> {
|
||||
|
|
@ -472,38 +317,11 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_model_lists_mirror_ts_constants() {
|
||||
fn claude_fallback_models_mirror_ts_constants() {
|
||||
let claude = fallback_claude_models();
|
||||
assert_eq!(claude.len(), 7);
|
||||
assert_eq!(claude[0].value, "claude-sonnet-4-6");
|
||||
assert_eq!(claude[6].display_name, "Claude 3.5 Haiku");
|
||||
let gemini = fallback_gemini_models();
|
||||
assert_eq!(gemini.len(), 5);
|
||||
assert_eq!(gemini[0].value, "gemini-3-pro-preview");
|
||||
assert_eq!(gemini[4].display_name, "Gemini 2.0 Flash");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_listing_filters_sorts_and_dedupes_like_ts() {
|
||||
let body = r#"{"models":[
|
||||
{"name":"models/gemini-2.0-flash","displayName":"Gemini 2.0 Flash","supportedGenerationMethods":["generateContent"]},
|
||||
{"name":"models/text-embedding-004","displayName":"Embedding","supportedGenerationMethods":["embedContent"]},
|
||||
{"name":"models/gemini-embedding-001","displayName":"Gemini Embedding","supportedGenerationMethods":["generateContent"]},
|
||||
{"name":"models/aqa","displayName":"AQA","supportedGenerationMethods":["generateContent"]},
|
||||
{"name":"models/gemini-1.0-pro","displayName":"Legacy","supportedGenerationMethods":["generateContent"]},
|
||||
{"name":"models/gemini-3-pro-preview","displayName":"Gemini 3 Pro","supportedGenerationMethods":["generateContent"]},
|
||||
{"name":"models/gemini-2.5-pro","displayName":"Gemini 2.5 Pro","supportedGenerationMethods":["generateContent"]},
|
||||
{"name":"models/gemini-2.5-pro","displayName":"Duplicate","supportedGenerationMethods":["generateContent"]}
|
||||
]}"#;
|
||||
let models = parse_gemini_models_json(body);
|
||||
let values: Vec<&str> = models.iter().map(|m| m.value.as_str()).collect();
|
||||
// gemini-3 first, then 2.5-pro, then 2.0; embed/aqa/legacy
|
||||
// skipped; duplicate deduped.
|
||||
assert_eq!(
|
||||
values,
|
||||
["gemini-3-pro-preview", "gemini-2.5-pro", "gemini-2.0-flash"]
|
||||
);
|
||||
assert_eq!(models[0].display_name, "Gemini 3 Pro");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -524,15 +342,4 @@ mod tests {
|
|||
// displayName mirrors the slug like the TS mapper.
|
||||
assert_eq!(models[0].display_name, "gpt-5.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_skip_filter_matches_ts_regex() {
|
||||
assert!(gemini_id_is_skipped("text-embedding-004"));
|
||||
assert!(gemini_id_is_skipped("aqa"));
|
||||
assert!(gemini_id_is_skipped("chat-bison-001"));
|
||||
assert!(gemini_id_is_skipped("gemini-1.0-pro"));
|
||||
assert!(!gemini_id_is_skipped("gemini-2.5-flash"));
|
||||
// `^` anchors: bison/legacy markers only skip at the start.
|
||||
assert!(!gemini_id_is_skipped("my-chat-bison"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,10 +169,6 @@ fn install_commands_mirror_install_agent_ts() {
|
|||
install_command(AgentProvider::CodexCli),
|
||||
"npm install -g @openai/codex"
|
||||
);
|
||||
assert_eq!(
|
||||
install_command(AgentProvider::GeminiCli),
|
||||
"npm install -g @anthropic-ai/gemini-cli"
|
||||
);
|
||||
let antigravity = if cfg!(windows) {
|
||||
"irm https://antigravity.google/cli/install.ps1 | iex"
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
|||
|
||||
use op_editor_core::editor_ui_state::{RecentFile, RECENT_FILE_CAP};
|
||||
use op_editor_core::{
|
||||
AcpAgentConfig, AcpConnectionType, AgentProvider, BuiltinAgentConfig, BuiltinAgentKind,
|
||||
BuiltinAgentPresetKey, EditorState, ImageGenProfile, ImageGenProvider, Locale, McpCli,
|
||||
ThemeMode,
|
||||
AcpAgentConfig, AcpConnectionType, BuiltinAgentConfig, BuiltinAgentKind, BuiltinAgentPresetKey,
|
||||
EditorState, ImageGenProfile, ImageGenProvider, Locale, ThemeMode,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -79,14 +78,14 @@ pub struct Fingerprint {
|
|||
theme: ThemeMode,
|
||||
locale: Locale,
|
||||
port: u16,
|
||||
cli: [bool; 8],
|
||||
cli: [bool; 7],
|
||||
images_adv: bool,
|
||||
openverse_client_id: String,
|
||||
openverse_client_secret: String,
|
||||
openverse_credential_owner: Option<String>,
|
||||
auto_update_enabled: bool,
|
||||
experimental_features_enabled: bool,
|
||||
connected: [bool; 7],
|
||||
connected: [bool; 6],
|
||||
builtin_agents: Vec<BuiltinAgentConfig>,
|
||||
acp_agents: Vec<AcpAgentConfig>,
|
||||
image_gen_profiles: Vec<ImageGenProfile>,
|
||||
|
|
@ -148,10 +147,8 @@ struct SettingsPayload {
|
|||
auto_update_enabled: Option<bool>,
|
||||
#[serde(default)]
|
||||
experimental_features_enabled: Option<bool>,
|
||||
/// Per-provider connect state, indexed by `AgentProvider::ALL`
|
||||
/// (Claude / Codex / OpenCode / Copilot / Gemini / Antigravity /
|
||||
/// Grok Build). Restored on
|
||||
/// launch so the chat model picker survives a restart.
|
||||
/// Per-provider connect state, indexed by `AgentProvider::ALL`.
|
||||
/// Restored on launch so the chat model picker survives a restart.
|
||||
#[serde(default)]
|
||||
connected: Option<Vec<bool>>,
|
||||
#[serde(default)]
|
||||
|
|
@ -259,10 +256,7 @@ fn apply_payload_with_options(
|
|||
eui.agent_settings.mcp_server.port = port.max(1024);
|
||||
}
|
||||
if let Some(flags) = payload.mcp_cli_enabled {
|
||||
eui.agent_settings.mcp_cli_enabled = [false; 8];
|
||||
for (index, enabled) in flags.into_iter().take(McpCli::ALL.len()).enumerate() {
|
||||
eui.agent_settings.mcp_cli_enabled[index] = enabled;
|
||||
}
|
||||
eui.agent_settings.mcp_cli_enabled = migrate_mcp_cli_flags(flags);
|
||||
}
|
||||
if let Some(b) = payload.images_advanced_open {
|
||||
eui.agent_settings.images_advanced_open = b;
|
||||
|
|
@ -279,10 +273,7 @@ fn apply_payload_with_options(
|
|||
eui.agent_settings.experimental_features_enabled = b;
|
||||
}
|
||||
if let Some(c) = payload.connected {
|
||||
eui.agent_settings.connected = [false; 7];
|
||||
for (index, connected) in c.into_iter().take(AgentProvider::ALL.len()).enumerate() {
|
||||
eui.agent_settings.connected[index] = connected;
|
||||
}
|
||||
eui.agent_settings.connected = migrate_connected_provider_flags(c);
|
||||
}
|
||||
if let Some(agents) = payload.builtin_agents {
|
||||
let agents = agents
|
||||
|
|
@ -365,6 +356,52 @@ fn apply_payload_with_options(
|
|||
state.rebuild_chat_models();
|
||||
}
|
||||
|
||||
/// Remove the retired Gemini CLI slot from positional v1 settings without
|
||||
/// shifting the providers that followed it. Released settings used either
|
||||
/// five slots (through Gemini) or seven slots (Gemini + Antigravity + Grok);
|
||||
/// the current six-slot layout omits Gemini.
|
||||
fn migrate_connected_provider_flags(flags: Vec<bool>) -> [bool; 6] {
|
||||
let mut migrated = [false; 6];
|
||||
match flags.len() {
|
||||
6 => migrated.copy_from_slice(&flags),
|
||||
5 => migrated[..4].copy_from_slice(&flags[..4]),
|
||||
7.. => {
|
||||
migrated[..4].copy_from_slice(&flags[..4]);
|
||||
migrated[4] = flags[5];
|
||||
migrated[5] = flags[6];
|
||||
}
|
||||
_ => {
|
||||
let unchanged = flags.len().min(4);
|
||||
migrated[..unchanged].copy_from_slice(&flags[..unchanged]);
|
||||
}
|
||||
}
|
||||
migrated
|
||||
}
|
||||
|
||||
/// Remove the retired Gemini CLI MCP slot from positional v1 settings.
|
||||
/// Historical layouts had six or eight slots; the current seven-slot layout
|
||||
/// keeps every other CLI in its original relative order.
|
||||
fn migrate_mcp_cli_flags(flags: Vec<bool>) -> [bool; 7] {
|
||||
let mut migrated = [false; 7];
|
||||
match flags.len() {
|
||||
7 => migrated.copy_from_slice(&flags),
|
||||
8.. => {
|
||||
migrated[0] = flags[0];
|
||||
migrated[1] = flags[1];
|
||||
migrated[2..].copy_from_slice(&flags[3..8]);
|
||||
}
|
||||
3..=6 => {
|
||||
migrated[0] = flags[0];
|
||||
migrated[1] = flags[1];
|
||||
migrated[2..flags.len() - 1].copy_from_slice(&flags[3..]);
|
||||
}
|
||||
_ => {
|
||||
migrated[..flags.len()].copy_from_slice(&flags);
|
||||
}
|
||||
}
|
||||
migrated
|
||||
}
|
||||
|
||||
fn builtin_agent_to_payload(agent: &BuiltinAgentConfig) -> BuiltinAgentPayload {
|
||||
BuiltinAgentPayload {
|
||||
id: agent.id.clone(),
|
||||
|
|
|
|||
|
|
@ -62,6 +62,31 @@ fn checked_settings_load_rejects_an_unsupported_settings_version() {
|
|||
let _ = std::fs::remove_dir_all(root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_settings_load_migrates_retired_gemini_cli_slots() {
|
||||
let root = checked_settings_test_path("retired-gemini-cli");
|
||||
let path = root.join("settings.json");
|
||||
std::fs::create_dir_all(&root).expect("create settings test directory");
|
||||
std::fs::write(
|
||||
&path,
|
||||
br#"{"version":1,"connected":[true,false,true,false,true,true,false],"mcp_cli_enabled":[true,false,true,false,true,false,true,true]}"#,
|
||||
)
|
||||
.expect("write legacy settings");
|
||||
let mut state = EditorState::new();
|
||||
|
||||
load_checked_from_path(&mut state, &path).expect("legacy v1 layout should migrate");
|
||||
|
||||
assert_eq!(
|
||||
state.editor_ui.agent_settings.connected,
|
||||
[true, false, true, false, true, false]
|
||||
);
|
||||
assert_eq!(
|
||||
state.editor_ui.agent_settings.mcp_cli_enabled,
|
||||
[true, false, false, true, false, true, true]
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_settings_load_rejects_unknown_fields_at_every_settings_nesting_level() {
|
||||
let cases = vec![
|
||||
|
|
|
|||
|
|
@ -109,9 +109,9 @@ fn imported_agents_are_excluded_from_persistence() {
|
|||
|
||||
#[test]
|
||||
fn connected_state_round_trips_through_payload() {
|
||||
// Connect Claude (0) + Gemini (4), leave the rest off.
|
||||
// Connect Claude (0) + Antigravity (4), leave the rest off.
|
||||
let mut src = EditorState::new();
|
||||
src.editor_ui.agent_settings.connected = [true, false, false, false, true, true, false];
|
||||
src.editor_ui.agent_settings.connected = [true, false, false, false, true, false];
|
||||
// Serialize → JSON → deserialize, the real on-disk path.
|
||||
let json = serde_json::to_string(&to_payload(&src)).unwrap();
|
||||
let payload: SettingsPayload = serde_json::from_str(&json).unwrap();
|
||||
|
|
@ -119,7 +119,7 @@ fn connected_state_round_trips_through_payload() {
|
|||
apply_payload(&mut dst, payload);
|
||||
assert_eq!(
|
||||
dst.editor_ui.agent_settings.connected,
|
||||
[true, false, false, false, true, true, false]
|
||||
[true, false, false, false, true, false]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -130,26 +130,58 @@ fn six_cli_mcp_payload_migrates_to_new_cli_count() {
|
|||
)
|
||||
.unwrap();
|
||||
let mut dst = EditorState::new();
|
||||
dst.editor_ui.agent_settings.mcp_cli_enabled = [true; 8];
|
||||
dst.editor_ui.agent_settings.mcp_cli_enabled = [true; 7];
|
||||
|
||||
apply_payload(&mut dst, payload);
|
||||
|
||||
assert_eq!(
|
||||
dst.editor_ui.agent_settings.mcp_cli_enabled,
|
||||
[true, false, true, false, true, true, false, false]
|
||||
[true, false, false, true, true, false, false]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_provider_connected_payload_migrates_to_new_provider_count() {
|
||||
fn eight_cli_mcp_payload_drops_gemini_without_shifting_later_clis() {
|
||||
let payload: SettingsPayload = serde_json::from_str(
|
||||
r#"{"version":1,"mcp_cli_enabled":[true,false,true,false,true,false,true,true]}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let mut dst = EditorState::new();
|
||||
|
||||
apply_payload(&mut dst, payload);
|
||||
|
||||
assert_eq!(
|
||||
dst.editor_ui.agent_settings.mcp_cli_enabled,
|
||||
[true, false, false, true, false, true, true]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_provider_connected_payload_drops_gemini_without_shifting_later_providers() {
|
||||
let payload: SettingsPayload = serde_json::from_str(
|
||||
r#"{"version":1,"connected":[true,false,true,false,true,true,false]}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let mut dst = EditorState::new();
|
||||
|
||||
apply_payload(&mut dst, payload);
|
||||
|
||||
assert_eq!(
|
||||
dst.editor_ui.agent_settings.connected,
|
||||
[true, false, true, false, true, false]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_provider_connected_payload_drops_retired_gemini() {
|
||||
let payload: SettingsPayload =
|
||||
serde_json::from_str(r#"{"version":1,"connected":[true,false,false,false,true]}"#).unwrap();
|
||||
let mut dst = EditorState::new();
|
||||
dst.editor_ui.agent_settings.connected = [false, false, false, false, false, true, true];
|
||||
dst.editor_ui.agent_settings.connected = [false, false, false, false, true, true];
|
||||
apply_payload(&mut dst, payload);
|
||||
assert_eq!(
|
||||
dst.editor_ui.agent_settings.connected,
|
||||
[true, false, false, false, true, false, false]
|
||||
[true, false, false, false, false, false]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +194,7 @@ fn legacy_settings_without_connected_field_default_to_disconnected() {
|
|||
let payload: SettingsPayload = serde_json::from_str(legacy).unwrap();
|
||||
let mut dst = EditorState::new();
|
||||
apply_payload(&mut dst, payload);
|
||||
assert_eq!(dst.editor_ui.agent_settings.connected, [false; 7]);
|
||||
assert_eq!(dst.editor_ui.agent_settings.connected, [false; 6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -965,7 +965,6 @@ fn parse_agent_provider(raw: &str) -> Option<op_editor_core::AgentProvider> {
|
|||
"codex" | "codexcli" => Some(AgentProvider::CodexCli),
|
||||
"opencode" => Some(AgentProvider::OpenCode),
|
||||
"githubcopilot" | "copilot" => Some(AgentProvider::GithubCopilot),
|
||||
"gemini" | "geminicli" => Some(AgentProvider::GeminiCli),
|
||||
"antigravity" | "agy" => Some(AgentProvider::Antigravity),
|
||||
"grok" | "grokbuild" => Some(AgentProvider::GrokBuild),
|
||||
_ => None,
|
||||
|
|
@ -982,7 +981,6 @@ fn provider_to_probe(
|
|||
AgentProvider::CodexCli => ProbeProvider::CodexCli,
|
||||
AgentProvider::OpenCode => ProbeProvider::OpenCode,
|
||||
AgentProvider::GithubCopilot => ProbeProvider::GithubCopilot,
|
||||
AgentProvider::GeminiCli => ProbeProvider::GeminiCli,
|
||||
AgentProvider::Antigravity => ProbeProvider::Antigravity,
|
||||
AgentProvider::GrokBuild => ProbeProvider::GrokBuild,
|
||||
}
|
||||
|
|
@ -1086,7 +1084,6 @@ fn provider_key(provider: op_editor_core::AgentProvider) -> &'static str {
|
|||
AgentProvider::CodexCli => "codex",
|
||||
AgentProvider::OpenCode => "opencode",
|
||||
AgentProvider::GithubCopilot => "github-copilot",
|
||||
AgentProvider::GeminiCli => "gemini",
|
||||
AgentProvider::Antigravity => "antigravity",
|
||||
AgentProvider::GrokBuild => "grok-build",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -817,6 +817,26 @@ fn web_cli_and_acp_connect_routes_are_unavailable_in_both_dispatchers() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retired_gemini_cli_connect_aliases_are_rejected_before_probe() {
|
||||
for provider in ["gemini", "gemini-cli"] {
|
||||
let mut state = fresh_state();
|
||||
let body = serde_json::json!({ "provider": provider }).to_string();
|
||||
|
||||
let reply = handle_provider_connect_request_with_probe(&body, &mut state, |_| {
|
||||
panic!("retired Gemini CLI request must not reach the probe")
|
||||
});
|
||||
|
||||
assert_eq!(reply.status, "400 Bad Request", "provider={provider}");
|
||||
assert!(state
|
||||
.editor
|
||||
.editor_ui
|
||||
.agent_settings
|
||||
.pending_provider_connect
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post_open_recent_loads_recent_path_and_bumps_version() {
|
||||
use op_editor_core::editor_ui_state::RecentFile;
|
||||
|
|
|
|||
|
|
@ -200,9 +200,6 @@ fn missing_models_connect_error(provider: AgentProvider) -> String {
|
|||
AgentProvider::GithubCopilot => {
|
||||
"No models found. GitHub Copilot did not return a model list.".to_string()
|
||||
}
|
||||
AgentProvider::GeminiCli => {
|
||||
"No models found. Gemini CLI did not return a model list.".to_string()
|
||||
}
|
||||
AgentProvider::Antigravity => {
|
||||
"No model available. Antigravity did not expose its default model.".to_string()
|
||||
}
|
||||
|
|
@ -218,7 +215,6 @@ fn provider_key(provider: AgentProvider) -> &'static str {
|
|||
AgentProvider::CodexCli => "codex",
|
||||
AgentProvider::OpenCode => "opencode",
|
||||
AgentProvider::GithubCopilot => "github-copilot",
|
||||
AgentProvider::GeminiCli => "gemini",
|
||||
AgentProvider::Antigravity => "antigravity",
|
||||
AgentProvider::GrokBuild => "grok-build",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -635,7 +635,7 @@ mod tests {
|
|||
.all(|entry| entry.builtin_provider_id.is_some()));
|
||||
assert_eq!(
|
||||
state.chat.available_models[1].provider,
|
||||
AgentProvider::GeminiCli
|
||||
AgentProvider::CodexCli
|
||||
);
|
||||
// Select the second model, then re-apply a re-ordered catalog — the
|
||||
// selection follows the entry by identity.
|
||||
|
|
@ -661,7 +661,7 @@ mod tests {
|
|||
assert_eq!(provider_for_model_id("gpt-5.5"), AgentProvider::CodexCli);
|
||||
assert_eq!(
|
||||
provider_for_model_id("gemini-2.5-pro"),
|
||||
AgentProvider::GeminiCli
|
||||
AgentProvider::CodexCli
|
||||
);
|
||||
assert_eq!(
|
||||
provider_for_model_id("copilot-fast"),
|
||||
|
|
|
|||
|
|
@ -109,13 +109,14 @@ fn parse_builtin_model_key(value: &str) -> Option<(&str, &str)> {
|
|||
/// Heuristic provider tag for a legacy daemon built-in model id.
|
||||
pub(crate) fn provider_for_model_id(id: &str) -> AgentProvider {
|
||||
let lower = id.to_ascii_lowercase();
|
||||
if lower.contains("gemini") {
|
||||
AgentProvider::GeminiCli
|
||||
} else if lower.contains("antigravity") {
|
||||
if lower.contains("antigravity") {
|
||||
AgentProvider::Antigravity
|
||||
} else if lower.contains("grok") {
|
||||
AgentProvider::GrokBuild
|
||||
} else if lower.contains("gpt") || lower.contains("codex") {
|
||||
} else if lower.contains("gpt") || lower.contains("codex") || lower.contains("gemini") {
|
||||
// Legacy bare model ids have no transport identity. Gemini API
|
||||
// built-ins use the generic OpenAI-compatible tag; they must not be
|
||||
// mislabeled as the retired Gemini CLI or as Antigravity.
|
||||
AgentProvider::CodexCli
|
||||
} else if lower.contains("copilot") {
|
||||
AgentProvider::GithubCopilot
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
use op_editor_core::editor_ui_state::{RecentFile, RECENT_FILE_CAP};
|
||||
use op_editor_core::{
|
||||
BuiltinAgentConfig, BuiltinAgentKind, BuiltinAgentPresetKey, EditorState, ImageGenProfile,
|
||||
ImageGenProvider, Locale, McpCli, ThemeMode,
|
||||
ImageGenProvider, Locale, ThemeMode,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ pub(crate) struct Fingerprint {
|
|||
theme: ThemeMode,
|
||||
locale: Locale,
|
||||
port: u16,
|
||||
cli: [bool; 8],
|
||||
cli: [bool; 7],
|
||||
images_adv: bool,
|
||||
auto_update_enabled: bool,
|
||||
experimental_features_enabled: bool,
|
||||
|
|
@ -250,10 +250,7 @@ fn apply_payload(state: &mut EditorState, payload: SettingsPayload) {
|
|||
eui.agent_settings.mcp_server.port = port.max(1024);
|
||||
}
|
||||
if let Some(flags) = payload.mcp_cli_enabled {
|
||||
eui.agent_settings.mcp_cli_enabled = [false; 8];
|
||||
for (index, enabled) in flags.into_iter().take(McpCli::ALL.len()).enumerate() {
|
||||
eui.agent_settings.mcp_cli_enabled[index] = enabled;
|
||||
}
|
||||
eui.agent_settings.mcp_cli_enabled = migrate_mcp_cli_flags(flags);
|
||||
}
|
||||
if let Some(open) = payload.images_advanced_open {
|
||||
eui.agent_settings.images_advanced_open = open;
|
||||
|
|
@ -321,6 +318,29 @@ fn apply_payload(state: &mut EditorState, payload: SettingsPayload) {
|
|||
state.rebuild_chat_models();
|
||||
}
|
||||
|
||||
/// Remove the retired Gemini CLI slot from positional browser settings.
|
||||
/// Released snapshots used six or eight entries; current snapshots use seven.
|
||||
fn migrate_mcp_cli_flags(flags: Vec<bool>) -> [bool; 7] {
|
||||
let mut migrated = [false; 7];
|
||||
match flags.len() {
|
||||
7 => migrated.copy_from_slice(&flags),
|
||||
8.. => {
|
||||
migrated[0] = flags[0];
|
||||
migrated[1] = flags[1];
|
||||
migrated[2..].copy_from_slice(&flags[3..8]);
|
||||
}
|
||||
3..=6 => {
|
||||
migrated[0] = flags[0];
|
||||
migrated[1] = flags[1];
|
||||
migrated[2..flags.len() - 1].copy_from_slice(&flags[3..]);
|
||||
}
|
||||
_ => {
|
||||
migrated[..flags.len()].copy_from_slice(&flags);
|
||||
}
|
||||
}
|
||||
migrated
|
||||
}
|
||||
|
||||
fn apply_credential_payload(state: &mut EditorState, payload: CredentialPayload) {
|
||||
let settings = &mut state.editor_ui.agent_settings;
|
||||
settings.builtin_agents = dedupe_builtin_agents(
|
||||
|
|
|
|||
|
|
@ -74,6 +74,18 @@ pub(super) fn prepare(raw: &str) -> PreparedSettings {
|
|||
.as_object_mut()
|
||||
.expect("browser settings object was checked above");
|
||||
let mut sanitized = removed_forbidden;
|
||||
if let Some(flags) = payload
|
||||
.mcp_cli_enabled
|
||||
.as_ref()
|
||||
.filter(|flags| flags.len() != op_editor_core::McpCli::ALL.len())
|
||||
{
|
||||
object.insert(
|
||||
"mcp_cli_enabled".into(),
|
||||
serde_json::to_value(super::migrate_mcp_cli_flags(flags.clone()))
|
||||
.expect("boolean array serialization cannot fail"),
|
||||
);
|
||||
sanitized = true;
|
||||
}
|
||||
for key in [
|
||||
"openverse_oauth",
|
||||
"builtin_agents",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,39 @@ fn settings_payload_round_trips_recent_files_and_mcp_preferences() {
|
|||
assert_eq!(dst.editor_ui.recent_files[0].path, "/tmp/a.op");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_mcp_flags_drop_gemini_without_shifting_google_antigravity() {
|
||||
let legacy = r#"{
|
||||
"version":1,
|
||||
"mcp_cli_enabled":[true,false,true,false,true,false,true,true]
|
||||
}"#;
|
||||
let mut state = EditorState::new();
|
||||
let mut writes = Vec::new();
|
||||
|
||||
let loaded = load_into_with(&mut state, Some(legacy), None, |key, json| {
|
||||
writes.push((key.to_string(), json.to_string()));
|
||||
true
|
||||
});
|
||||
|
||||
assert!(
|
||||
!loaded.loaded,
|
||||
"ordinary settings do not contain a credential payload"
|
||||
);
|
||||
assert_eq!(
|
||||
state.editor_ui.agent_settings.mcp_cli_enabled,
|
||||
[true, false, false, true, false, true, true]
|
||||
);
|
||||
let rewritten = writes
|
||||
.iter()
|
||||
.find_map(|(key, json)| (key == STORAGE_KEY).then_some(json))
|
||||
.expect("legacy positional settings should be rewritten");
|
||||
let value: serde_json::Value = serde_json::from_str(rewritten).expect("rewritten settings");
|
||||
assert_eq!(
|
||||
value["mcp_cli_enabled"],
|
||||
serde_json::json!([true, false, false, true, false, true, true])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn separate_settings_and_credential_payloads_round_trip_their_own_fields() {
|
||||
let mut src = EditorState::new();
|
||||
|
|
|
|||
|
|
@ -185,9 +185,9 @@ fn validate_general_semantics(payload: &SettingsPayload) -> Result<(), String> {
|
|||
if payload
|
||||
.mcp_cli_enabled
|
||||
.as_ref()
|
||||
.is_some_and(|enabled| enabled.len() != McpCli::ALL.len())
|
||||
.is_some_and(|enabled| !matches!(enabled.len(), 6..=8))
|
||||
{
|
||||
return Err("browser MCP CLI flags would be normalized".into());
|
||||
return Err("unsupported browser MCP CLI flag layout".into());
|
||||
}
|
||||
if payload
|
||||
.recent_files
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ fn seed_two_chat_models(host: &mut WidgetHost) {
|
|||
.chat
|
||||
.available_models
|
||||
.push(ModelEntry::new(
|
||||
AgentProvider::GeminiCli,
|
||||
"gemini-2.5-pro",
|
||||
"Gemini 2.5 Pro",
|
||||
AgentProvider::Antigravity,
|
||||
"default",
|
||||
"Antigravity Default",
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ LLM-Anbieter",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "GitHub Copilot-Modelle",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Google Gemini-Modelle",
|
||||
"agents.mcpServer" => "MCP-Server",
|
||||
"agents.mcpServerStart" => "Starten",
|
||||
"agents.mcpServerStop" => "Stoppen",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "OpenAI-Modelle",
|
||||
"settings.provider.openCode" => "Über 75 LLM-Anbieter",
|
||||
"settings.provider.githubCopilot" => "GitHub-Copilot-Modelle",
|
||||
"settings.provider.geminiCli" => "Google-Gemini-Modelle",
|
||||
"settings.provider.antigravity" => "Antigravity-Modelle",
|
||||
"settings.provider.grokBuild" => "Grok-Build-Modelle",
|
||||
"settings.system.title" => "System",
|
||||
|
|
|
|||
|
|
@ -429,8 +429,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ LLM providers",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "GitHub Copilot models",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Google Gemini models",
|
||||
"agents.mcpServer" => "MCP Server",
|
||||
"agents.mcpServerStart" => "Start",
|
||||
"agents.mcpServerStop" => "Stop",
|
||||
|
|
@ -633,7 +631,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "OpenAI models",
|
||||
"settings.provider.openCode" => "75+ LLM providers",
|
||||
"settings.provider.githubCopilot" => "GitHub Copilot models",
|
||||
"settings.provider.geminiCli" => "Google Gemini models",
|
||||
"settings.provider.antigravity" => "Antigravity models",
|
||||
"settings.provider.grokBuild" => "Grok Build models",
|
||||
"settings.system.title" => "System",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ proveedores LLM",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "Modelos GitHub Copilot",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Modelos Google Gemini",
|
||||
"agents.mcpServer" => "Servidor MCP",
|
||||
"agents.mcpServerStart" => "Iniciar",
|
||||
"agents.mcpServerStop" => "Detener",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "Modelos OpenAI",
|
||||
"settings.provider.openCode" => "Más de 75 proveedores de LLM",
|
||||
"settings.provider.githubCopilot" => "Modelos GitHub Copilot",
|
||||
"settings.provider.geminiCli" => "Modelos Google Gemini",
|
||||
"settings.provider.antigravity" => "Modelos Antigravity",
|
||||
"settings.provider.grokBuild" => "Modelos Grok Build",
|
||||
"settings.system.title" => "Sistema",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ fournisseurs LLM",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "Modèles GitHub Copilot",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Modèles Google Gemini",
|
||||
"agents.mcpServer" => "Serveur MCP",
|
||||
"agents.mcpServerStart" => "Démarrer",
|
||||
"agents.mcpServerStop" => "Arrêter",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "Modèles OpenAI",
|
||||
"settings.provider.openCode" => "Plus de 75 fournisseurs de LLM",
|
||||
"settings.provider.githubCopilot" => "Modèles GitHub Copilot",
|
||||
"settings.provider.geminiCli" => "Modèles Google Gemini",
|
||||
"settings.provider.antigravity" => "Modèles Antigravity",
|
||||
"settings.provider.grokBuild" => "Modèles Grok Build",
|
||||
"settings.system.title" => "Système",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ LLM प्रदाता",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "GitHub Copilot मॉडल",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Google Gemini मॉडल",
|
||||
"agents.mcpServer" => "MCP सर्वर",
|
||||
"agents.mcpServerStart" => "शुरू करें",
|
||||
"agents.mcpServerStop" => "रोकें",
|
||||
|
|
@ -590,7 +588,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "OpenAI मॉडल",
|
||||
"settings.provider.openCode" => "75+ LLM प्रदाता",
|
||||
"settings.provider.githubCopilot" => "GitHub Copilot मॉडल",
|
||||
"settings.provider.geminiCli" => "Google Gemini मॉडल",
|
||||
"settings.provider.antigravity" => "Antigravity मॉडल",
|
||||
"settings.provider.grokBuild" => "Grok Build मॉडल",
|
||||
"settings.system.title" => "सिस्टम",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ penyedia LLM",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "Model GitHub Copilot",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Model Google Gemini",
|
||||
"agents.mcpServer" => "Server MCP",
|
||||
"agents.mcpServerStart" => "Mulai",
|
||||
"agents.mcpServerStop" => "Hentikan",
|
||||
|
|
@ -590,7 +588,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "Model OpenAI",
|
||||
"settings.provider.openCode" => "75+ penyedia LLM",
|
||||
"settings.provider.githubCopilot" => "Model GitHub Copilot",
|
||||
"settings.provider.geminiCli" => "Model Google Gemini",
|
||||
"settings.provider.antigravity" => "Model Antigravity",
|
||||
"settings.provider.grokBuild" => "Model Grok Build",
|
||||
"settings.system.title" => "Sistem",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75 以上の LLM プロバイダー",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "GitHub Copilot モデル",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Google Gemini モデル",
|
||||
"agents.mcpServer" => "MCP サーバー",
|
||||
"agents.mcpServerStart" => "開始",
|
||||
"agents.mcpServerStop" => "停止",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "OpenAI モデル",
|
||||
"settings.provider.openCode" => "75+ の LLM プロバイダー",
|
||||
"settings.provider.githubCopilot" => "GitHub Copilot モデル",
|
||||
"settings.provider.geminiCli" => "Google Gemini モデル",
|
||||
"settings.provider.antigravity" => "Antigravity モデル",
|
||||
"settings.provider.grokBuild" => "Grok Build モデル",
|
||||
"settings.system.title" => "システム",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75개 이상의 LLM 제공자",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "GitHub Copilot 모델",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Google Gemini 모델",
|
||||
"agents.mcpServer" => "MCP 서버",
|
||||
"agents.mcpServerStart" => "시작",
|
||||
"agents.mcpServerStop" => "정지",
|
||||
|
|
@ -590,7 +588,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "OpenAI 모델",
|
||||
"settings.provider.openCode" => "75개 이상의 LLM 제공자",
|
||||
"settings.provider.githubCopilot" => "GitHub Copilot 모델",
|
||||
"settings.provider.geminiCli" => "Google Gemini 모델",
|
||||
"settings.provider.antigravity" => "Antigravity 모델",
|
||||
"settings.provider.grokBuild" => "Grok Build 모델",
|
||||
"settings.system.title" => "시스템",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ provedores de LLM",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "Modelos GitHub Copilot",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Modelos Google Gemini",
|
||||
"agents.mcpServer" => "Servidor MCP",
|
||||
"agents.mcpServerStart" => "Iniciar",
|
||||
"agents.mcpServerStop" => "Parar",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "Modelos OpenAI",
|
||||
"settings.provider.openCode" => "Mais de 75 provedores de LLM",
|
||||
"settings.provider.githubCopilot" => "Modelos GitHub Copilot",
|
||||
"settings.provider.geminiCli" => "Modelos Google Gemini",
|
||||
"settings.provider.antigravity" => "Modelos Antigravity",
|
||||
"settings.provider.grokBuild" => "Modelos Grok Build",
|
||||
"settings.system.title" => "Sistema",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ провайдеров LLM",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "Модели GitHub Copilot",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Модели Google Gemini",
|
||||
"agents.mcpServer" => "MCP Сервер",
|
||||
"agents.mcpServerStart" => "Запустить",
|
||||
"agents.mcpServerStop" => "Остановить",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "Модели OpenAI",
|
||||
"settings.provider.openCode" => "Более 75 провайдеров LLM",
|
||||
"settings.provider.githubCopilot" => "Модели GitHub Copilot",
|
||||
"settings.provider.geminiCli" => "Модели Google Gemini",
|
||||
"settings.provider.antigravity" => "Модели Antigravity",
|
||||
"settings.provider.grokBuild" => "Модели Grok Build",
|
||||
"settings.system.title" => "Система",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ ผู้ให้บริการ LLM",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "โมเดล GitHub Copilot",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "โมเดล Google Gemini",
|
||||
"agents.mcpServer" => "เซิร์ฟเวอร์ MCP",
|
||||
"agents.mcpServerStart" => "เริ่ม",
|
||||
"agents.mcpServerStop" => "หยุด",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "โมเดล OpenAI",
|
||||
"settings.provider.openCode" => "ผู้ให้บริการ LLM มากกว่า 75 ราย",
|
||||
"settings.provider.githubCopilot" => "โมเดล GitHub Copilot",
|
||||
"settings.provider.geminiCli" => "โมเดล Google Gemini",
|
||||
"settings.provider.antigravity" => "โมเดล Antigravity",
|
||||
"settings.provider.grokBuild" => "โมเดล Grok Build",
|
||||
"settings.system.title" => "ระบบ",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ LLM sağlayıcı",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "GitHub Copilot modelleri",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Google Gemini modelleri",
|
||||
"agents.mcpServer" => "MCP Sunucu",
|
||||
"agents.mcpServerStart" => "Başlat",
|
||||
"agents.mcpServerStop" => "Durdur",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "OpenAI modelleri",
|
||||
"settings.provider.openCode" => "75'ten fazla LLM sağlayıcısı",
|
||||
"settings.provider.githubCopilot" => "GitHub Copilot modelleri",
|
||||
"settings.provider.geminiCli" => "Google Gemini modelleri",
|
||||
"settings.provider.antigravity" => "Antigravity modelleri",
|
||||
"settings.provider.grokBuild" => "Grok Build modelleri",
|
||||
"settings.system.title" => "Sistem",
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ nhà cung cấp LLM",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "Các mô hình GitHub Copilot",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Các mô hình Google Gemini",
|
||||
"agents.mcpServer" => "Máy chủ MCP",
|
||||
"agents.mcpServerStart" => "Khởi động",
|
||||
"agents.mcpServerStop" => "Dừng",
|
||||
|
|
@ -592,7 +590,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "Mô hình OpenAI",
|
||||
"settings.provider.openCode" => "Hơn 75 nhà cung cấp LLM",
|
||||
"settings.provider.githubCopilot" => "Mô hình GitHub Copilot",
|
||||
"settings.provider.geminiCli" => "Mô hình Google Gemini",
|
||||
"settings.provider.antigravity" => "Mô hình Antigravity",
|
||||
"settings.provider.grokBuild" => "Mô hình Grok Build",
|
||||
"settings.system.title" => "Hệ thống",
|
||||
|
|
|
|||
|
|
@ -433,8 +433,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ LLM 提供商",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "GitHub Copilot 模型",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Google Gemini 模型",
|
||||
"agents.mcpServer" => "MCP 服务器",
|
||||
"agents.mcpServerStart" => "启动",
|
||||
"agents.mcpServerStop" => "停止",
|
||||
|
|
@ -639,7 +637,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "OpenAI 模型",
|
||||
"settings.provider.openCode" => "75+ LLM 提供商",
|
||||
"settings.provider.githubCopilot" => "GitHub Copilot 模型",
|
||||
"settings.provider.geminiCli" => "Google Gemini 模型",
|
||||
"settings.provider.antigravity" => "Antigravity 模型",
|
||||
"settings.provider.grokBuild" => "Grok Build 模型",
|
||||
"settings.system.title" => "系统",
|
||||
|
|
|
|||
|
|
@ -412,8 +412,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"agents.opencodeDesc" => "75+ LLM 供應商",
|
||||
"agents.copilot" => "GitHub Copilot",
|
||||
"agents.copilotDesc" => "GitHub Copilot 模型",
|
||||
"agents.geminiCli" => "Gemini CLI",
|
||||
"agents.geminiDesc" => "Google Gemini 模型",
|
||||
"agents.mcpServer" => "MCP 伺服器",
|
||||
"agents.mcpServerStart" => "啟動",
|
||||
"agents.mcpServerStop" => "停止",
|
||||
|
|
@ -608,7 +606,6 @@ pub fn lookup(key: &str) -> Option<&'static str> {
|
|||
"settings.provider.codexCli" => "OpenAI 模型",
|
||||
"settings.provider.openCode" => "75+ LLM 供應商",
|
||||
"settings.provider.githubCopilot" => "GitHub Copilot 模型",
|
||||
"settings.provider.geminiCli" => "Google Gemini 模型",
|
||||
"settings.provider.antigravity" => "Antigravity 模型",
|
||||
"settings.provider.grokBuild" => "Grok Build 模型",
|
||||
"settings.system.title" => "系統",
|
||||
|
|
|
|||
|
|
@ -362,9 +362,9 @@ validate_cli_bundle_version_template() {
|
|||
bundle=crates/op-cli/assets/skill-bundle.json
|
||||
sentinel=__OPENPENCIL_VERSION__
|
||||
sentinel_count=$(rg --fixed-strings --count-matches -- "$sentinel" "$bundle" || true)
|
||||
if [[ "${sentinel_count:-0}" != 6 ]]; then
|
||||
if [[ "${sentinel_count:-0}" != 5 ]]; then
|
||||
report_missing "$bundle" \
|
||||
"expected exactly 6 version sentinels ${sentinel} (found ${sentinel_count:-0})"
|
||||
"expected exactly 5 version sentinels ${sentinel} (found ${sentinel_count:-0})"
|
||||
fi
|
||||
reject_matches fixed "$bundle" "$current_version" \
|
||||
'embedded CLI bundle must not contain the canonical version literal'
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ new_repo() {
|
|||
printf '%s\n' '{"name":"fixture-packages"}' > "$repo/packages/package.json"
|
||||
|
||||
cat > "$repo/crates/op-cli/assets/skill-bundle.json" <<'JSON'
|
||||
{"one":"__OPENPENCIL_VERSION__","two":"__OPENPENCIL_VERSION__","three":"__OPENPENCIL_VERSION__","four":"__OPENPENCIL_VERSION__","five":"__OPENPENCIL_VERSION__","six":"__OPENPENCIL_VERSION__"}
|
||||
{"one":"__OPENPENCIL_VERSION__","two":"__OPENPENCIL_VERSION__","three":"__OPENPENCIL_VERSION__","four":"__OPENPENCIL_VERSION__","five":"__OPENPENCIL_VERSION__"}
|
||||
JSON
|
||||
cat > "$repo/crates/op-editor-core/src/state.rs" <<'RUST'
|
||||
version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||
|
|
@ -690,15 +690,15 @@ assert_no_success_output 'mismatched release tag'
|
|||
pass 'mismatched v* release tags are rejected outside the release workflow too'
|
||||
|
||||
repo=$(new_repo cli_bundle_wrong_sentinel_count 0.8.1)
|
||||
sed -i.bak 's/,"six":"__OPENPENCIL_VERSION__"//' \
|
||||
sed -i.bak 's/,"five":"__OPENPENCIL_VERSION__"//' \
|
||||
"$repo/crates/op-cli/assets/skill-bundle.json"
|
||||
rm "$repo/crates/op-cli/assets/skill-bundle.json.bak"
|
||||
run_guard "$repo"
|
||||
assert_status 1 'CLI bundle sentinel count'
|
||||
assert_contains 'crates/op-cli/assets/skill-bundle.json:1: error: expected exactly 6 version sentinels' \
|
||||
assert_contains 'crates/op-cli/assets/skill-bundle.json:1: error: expected exactly 5 version sentinels' \
|
||||
'CLI bundle sentinel count'
|
||||
assert_no_success_output 'CLI bundle sentinel count'
|
||||
pass 'embedded CLI bundle retains exactly six version sentinels'
|
||||
pass 'embedded CLI bundle retains exactly five version sentinels'
|
||||
|
||||
repo=$(new_repo cli_bundle_without_sentinels 0.8.1)
|
||||
sed -i.bak 's/__OPENPENCIL_VERSION__/__MISSING_VERSION__/g' \
|
||||
|
|
@ -706,7 +706,7 @@ sed -i.bak 's/__OPENPENCIL_VERSION__/__MISSING_VERSION__/g' \
|
|||
rm "$repo/crates/op-cli/assets/skill-bundle.json.bak"
|
||||
run_guard "$repo"
|
||||
assert_status 1 'CLI bundle missing sentinels'
|
||||
assert_contains 'crates/op-cli/assets/skill-bundle.json:1: error: expected exactly 6 version sentinels' \
|
||||
assert_contains 'crates/op-cli/assets/skill-bundle.json:1: error: expected exactly 5 version sentinels' \
|
||||
'CLI bundle missing sentinels'
|
||||
assert_contains '(found 0)' 'CLI bundle missing sentinels'
|
||||
assert_no_success_output 'CLI bundle missing sentinels'
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ KEYS = [
|
|||
"settings.images.clientSecretPlaceholder", "settings.images.registerLink",
|
||||
"settings.images.test", "settings.images.generation", "settings.images.add",
|
||||
"settings.images.empty", "settings.provider.claudeCode", "settings.provider.codexCli",
|
||||
"settings.provider.openCode", "settings.provider.githubCopilot", "settings.provider.geminiCli",
|
||||
"settings.provider.openCode", "settings.provider.githubCopilot",
|
||||
"settings.system.title", "settings.system.autoUpdate", "settings.system.upToDate",
|
||||
"settings.system.upToDateDescription", "settings.system.checking",
|
||||
"settings.system.checkingDescription", "settings.system.updateAvailable",
|
||||
|
|
@ -86,7 +86,6 @@ EN = {
|
|||
"settings.provider.codexCli": "OpenAI models",
|
||||
"settings.provider.openCode": "75+ LLM providers",
|
||||
"settings.provider.githubCopilot": "GitHub Copilot models",
|
||||
"settings.provider.geminiCli": "Google Gemini models",
|
||||
"settings.system.title": "System",
|
||||
"settings.system.autoUpdate": "Auto-update",
|
||||
"settings.system.upToDate": "Up to date",
|
||||
|
|
@ -139,7 +138,6 @@ ZH_CN = {
|
|||
"settings.provider.codexCli": "OpenAI 模型",
|
||||
"settings.provider.openCode": "75+ LLM 提供商",
|
||||
"settings.provider.githubCopilot": "GitHub Copilot 模型",
|
||||
"settings.provider.geminiCli": "Google Gemini 模型",
|
||||
"settings.system.title": "系统",
|
||||
"settings.system.autoUpdate": "自动更新",
|
||||
"settings.system.upToDate": "已是最新",
|
||||
|
|
@ -192,7 +190,6 @@ ZH_TW = {
|
|||
"settings.provider.codexCli": "OpenAI 模型",
|
||||
"settings.provider.openCode": "75+ LLM 供應商",
|
||||
"settings.provider.githubCopilot": "GitHub Copilot 模型",
|
||||
"settings.provider.geminiCli": "Google Gemini 模型",
|
||||
"settings.system.title": "系統",
|
||||
"settings.system.autoUpdate": "自動更新",
|
||||
"settings.system.upToDate": "已是最新",
|
||||
|
|
@ -247,7 +244,6 @@ JA = {
|
|||
"settings.provider.codexCli": "OpenAI モデル",
|
||||
"settings.provider.openCode": "75+ の LLM プロバイダー",
|
||||
"settings.provider.githubCopilot": "GitHub Copilot モデル",
|
||||
"settings.provider.geminiCli": "Google Gemini モデル",
|
||||
"settings.system.title": "システム",
|
||||
"settings.system.autoUpdate": "自動更新",
|
||||
"settings.system.upToDate": "最新です",
|
||||
|
|
@ -302,7 +298,6 @@ KO = {
|
|||
"settings.provider.codexCli": "OpenAI 모델",
|
||||
"settings.provider.openCode": "75개 이상의 LLM 제공자",
|
||||
"settings.provider.githubCopilot": "GitHub Copilot 모델",
|
||||
"settings.provider.geminiCli": "Google Gemini 모델",
|
||||
"settings.system.title": "시스템",
|
||||
"settings.system.autoUpdate": "자동 업데이트",
|
||||
"settings.system.upToDate": "최신 상태",
|
||||
|
|
@ -356,7 +351,6 @@ FR = {
|
|||
"settings.provider.codexCli": "Modèles OpenAI",
|
||||
"settings.provider.openCode": "Plus de 75 fournisseurs de LLM",
|
||||
"settings.provider.githubCopilot": "Modèles GitHub Copilot",
|
||||
"settings.provider.geminiCli": "Modèles Google Gemini",
|
||||
"settings.system.title": "Système",
|
||||
"settings.system.autoUpdate": "Mise à jour automatique",
|
||||
"settings.system.upToDate": "À jour",
|
||||
|
|
@ -411,7 +405,6 @@ ES = {
|
|||
"settings.provider.codexCli": "Modelos OpenAI",
|
||||
"settings.provider.openCode": "Más de 75 proveedores de LLM",
|
||||
"settings.provider.githubCopilot": "Modelos GitHub Copilot",
|
||||
"settings.provider.geminiCli": "Modelos Google Gemini",
|
||||
"settings.system.title": "Sistema",
|
||||
"settings.system.autoUpdate": "Actualización automática",
|
||||
"settings.system.upToDate": "Actualizado",
|
||||
|
|
@ -466,7 +459,6 @@ DE = {
|
|||
"settings.provider.codexCli": "OpenAI-Modelle",
|
||||
"settings.provider.openCode": "Über 75 LLM-Anbieter",
|
||||
"settings.provider.githubCopilot": "GitHub-Copilot-Modelle",
|
||||
"settings.provider.geminiCli": "Google-Gemini-Modelle",
|
||||
"settings.system.title": "System",
|
||||
"settings.system.autoUpdate": "Automatische Updates",
|
||||
"settings.system.upToDate": "Aktuell",
|
||||
|
|
@ -521,7 +513,6 @@ PT = {
|
|||
"settings.provider.codexCli": "Modelos OpenAI",
|
||||
"settings.provider.openCode": "Mais de 75 provedores de LLM",
|
||||
"settings.provider.githubCopilot": "Modelos GitHub Copilot",
|
||||
"settings.provider.geminiCli": "Modelos Google Gemini",
|
||||
"settings.system.title": "Sistema",
|
||||
"settings.system.autoUpdate": "Atualização automática",
|
||||
"settings.system.upToDate": "Atualizado",
|
||||
|
|
@ -576,7 +567,6 @@ RU = {
|
|||
"settings.provider.codexCli": "Модели OpenAI",
|
||||
"settings.provider.openCode": "Более 75 провайдеров LLM",
|
||||
"settings.provider.githubCopilot": "Модели GitHub Copilot",
|
||||
"settings.provider.geminiCli": "Модели Google Gemini",
|
||||
"settings.system.title": "Система",
|
||||
"settings.system.autoUpdate": "Автообновление",
|
||||
"settings.system.upToDate": "Актуально",
|
||||
|
|
@ -631,7 +621,6 @@ HI = {
|
|||
"settings.provider.codexCli": "OpenAI मॉडल",
|
||||
"settings.provider.openCode": "75+ LLM प्रदाता",
|
||||
"settings.provider.githubCopilot": "GitHub Copilot मॉडल",
|
||||
"settings.provider.geminiCli": "Google Gemini मॉडल",
|
||||
"settings.system.title": "सिस्टम",
|
||||
"settings.system.autoUpdate": "स्वतः अपडेट",
|
||||
"settings.system.upToDate": "नवीनतम",
|
||||
|
|
@ -686,7 +675,6 @@ TR = {
|
|||
"settings.provider.codexCli": "OpenAI modelleri",
|
||||
"settings.provider.openCode": "75'ten fazla LLM sağlayıcısı",
|
||||
"settings.provider.githubCopilot": "GitHub Copilot modelleri",
|
||||
"settings.provider.geminiCli": "Google Gemini modelleri",
|
||||
"settings.system.title": "Sistem",
|
||||
"settings.system.autoUpdate": "Otomatik güncelleme",
|
||||
"settings.system.upToDate": "Güncel",
|
||||
|
|
@ -740,7 +728,6 @@ TH = {
|
|||
"settings.provider.codexCli": "โมเดล OpenAI",
|
||||
"settings.provider.openCode": "ผู้ให้บริการ LLM มากกว่า 75 ราย",
|
||||
"settings.provider.githubCopilot": "โมเดล GitHub Copilot",
|
||||
"settings.provider.geminiCli": "โมเดล Google Gemini",
|
||||
"settings.system.title": "ระบบ",
|
||||
"settings.system.autoUpdate": "อัปเดตอัตโนมัติ",
|
||||
"settings.system.upToDate": "เป็นเวอร์ชันล่าสุด",
|
||||
|
|
@ -795,7 +782,6 @@ VI = {
|
|||
"settings.provider.codexCli": "Mô hình OpenAI",
|
||||
"settings.provider.openCode": "Hơn 75 nhà cung cấp LLM",
|
||||
"settings.provider.githubCopilot": "Mô hình GitHub Copilot",
|
||||
"settings.provider.geminiCli": "Mô hình Google Gemini",
|
||||
"settings.system.title": "Hệ thống",
|
||||
"settings.system.autoUpdate": "Tự động cập nhật",
|
||||
"settings.system.upToDate": "Đã cập nhật",
|
||||
|
|
@ -850,7 +836,6 @@ ID = {
|
|||
"settings.provider.codexCli": "Model OpenAI",
|
||||
"settings.provider.openCode": "75+ penyedia LLM",
|
||||
"settings.provider.githubCopilot": "Model GitHub Copilot",
|
||||
"settings.provider.geminiCli": "Model Google Gemini",
|
||||
"settings.system.title": "Sistem",
|
||||
"settings.system.autoUpdate": "Pembaruan otomatis",
|
||||
"settings.system.upToDate": "Terbaru",
|
||||
|
|
|
|||
Loading…
Reference in a new issue