mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-07 06:02:43 +08:00
fix: address code review findings
- validate chat image attachments server-side: only files inside the ~/.cloudcli/assets upload store may reach provider file reads - harden asset serving with nosniff and attachment disposition for SVGs to prevent stored XSS - show the timestamp on image-only user messages - serialize git stage/unstage calls and defer the status re-sync so rapid toggles can't interleave or flicker - use a literal hex fallback for commit ref badges (alpha suffix on a var() string produced invalid CSS) - stop binding a URL session to a guening project instead - add the missing attentionRequiredIndicator key to all sidebar locales - clean up dangling conjunctions left the de/ru/tr/ja/zh-CN/zh-TW READMEs
This commit is contained in:
10
README.de.md
10
README.de.md
@@ -62,7 +62,7 @@
|
||||
- **Sitzungsverwaltung** – Gespräche fortsetzen, mehrere Sitzungen verwalten und Verlauf nachverfolgen
|
||||
- **Plugin-System** – CloudCLI mit eigenen Plugins erweitern – neue Tabs, Backend-Dienste und Integrationen hinzufügen. [Eigenes Plugin erstellen →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
|
||||
- **TaskMaster AI Integration** *(Optional)* – Erweitertes Projektmanagement mit KI-gestützter Aufgabenplanung, PRD-Parsing und Workflow-Automatisierung
|
||||
- **Modell-Kompatibilität** – Funktioniert mit Claude, GPT und (vollständige Liste unterstützter Modelle zur Laufzeit über `GET /api/providers/:provider/models`)
|
||||
- **Modell-Kompatibilität** – Funktioniert mit den Claude- und GPT-Modellfamilien (vollständige Liste unterstützter Modelle zur Laufzeit über `GET /api/providers/:provider/models`)
|
||||
|
||||
|
||||
## Schnellstart
|
||||
@@ -103,7 +103,7 @@ Agents in isolierten Sandboxes mit Hypervisor-Isolation ausführen. Standardmä
|
||||
npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
|
||||
```
|
||||
|
||||
Unterstützt Claude Code, Codex und. Weitere Details in der [Sandbox-Dokumentation](docker/).
|
||||
Unterstützt Claude Code und Codex. Weitere Details in der [Sandbox-Dokumentation](docker/).
|
||||
|
||||
---
|
||||
|
||||
@@ -166,7 +166,7 @@ CloudCLI verfügt über ein Plugin-System, mit dem benutzerdefinierte Tabs mit e
|
||||
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Zeigt Dateianzahl, Codezeilen, Dateityp-Aufschlüsselung, größte Dateien und zuletzt geänderte Dateien des aktuellen Projekts |
|
||||
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Vollwertiges xterm.js-Terminal mit Multi-Tab-Unterstützung |
|
||||
| **[Claude Watch](https://github.com/satsuki19980613/cloudcli-claude-watch)** | Überwacht lange laufende Claude-Code-Sitzungen auf Hänger und stellt Prozesssteuerungen bereit |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | Erstellt arbeitsbereichsbezogene geplante Prompts und führt sie über eine lokale CLI wie Codex, Claude Code oder aus |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | Erstellt arbeitsbereichsbezogene geplante Prompts und führt sie über eine lokale CLI wie Codex oder Claude Code aus |
|
||||
| **[PRISM CloudCLI](https://github.com/jakeefr/cloudcli-plugin-prism)** | Sitzungsintelligenz für Claude Code in CloudCLI, inklusive Sichtbarkeit des Token-Verbrauchs |
|
||||
| **[Sessions](https://github.com/strykereye2/cloudcli-plugin-session-manager)** | Aktive Claude-Code-Sitzungen anzeigen, verwalten und beenden |
|
||||
| **[Token Cost Calculator](https://github.com/NightmareAway/cloudcli-plugin-token-cost-calculator)** | API-Kosten anhand von Modellpreisen und Token-Nutzung berechnen, mit Unterstützung für Preisvorlagen |
|
||||
@@ -193,7 +193,7 @@ Das bedeutet in der Praxis:
|
||||
|
||||
- **Alle Sitzungen, nicht nur eine** – CloudCLI UI erkennt automatisch jede Sitzung aus dem `~/.claude`-Ordner. Remote Control stellt nur die einzelne aktive Sitzung bereit, um sie in der Claude Mobile App verfügbar zu machen.
|
||||
- **Deine Einstellungen sind deine Einstellungen** – MCP-Server, Tool-Berechtigungen und Projektkonfiguration, die in CloudCLI UI geändert werden, werden direkt in die Claude Code-Konfiguration geschrieben und treten sofort in Kraft – und umgekehrt.
|
||||
- **Funktioniert mit mehr Agents** – Claude Code, Cursor CLI, Codex und, nicht nur Claude Code.
|
||||
- **Funktioniert mit mehr Agents** – Claude Code, Cursor CLI und Codex, nicht nur Claude Code.
|
||||
- **Vollständige UI, nicht nur ein Chat-Fenster** – Datei-Explorer, Git-Integration, MCP-Verwaltung und ein Shell-Terminal sind alle eingebaut.
|
||||
- **CloudCLI Cloud läuft in der Cloud** – Laptop zuklappen, der Agent läuft weiter. Kein Terminal zu überwachen, kein Rechner, der laufen muss.
|
||||
|
||||
@@ -202,7 +202,7 @@ Das bedeutet in der Praxis:
|
||||
<details>
|
||||
<summary>Muss ich ein KI-Abonnement separat bezahlen?</summary>
|
||||
|
||||
Ja. CloudCLI stellt die Umgebung bereit, nicht die KI. Du bringst dein eigenes Claude-, Cursor-, Codex- oder -Abonnement mit. CloudCLI Cloud beginnt bei €7/Monat für die gehostete Umgebung zusätzlich dazu.
|
||||
Ja. CloudCLI stellt die Umgebung bereit, nicht die KI. Du bringst dein eigenes Claude-, Cursor- oder Codex-Abonnement mit. CloudCLI Cloud beginnt bei €7/Monat für die gehostete Umgebung zusätzlich dazu.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
14
README.ja.md
14
README.ja.md
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
|
||||
<h1>Cloud CLI(別名 Claude Code UI)</h1>
|
||||
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>、<a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>、<a href="https://developers.openai.com/codex">Codex</a>、 のためのデスクトップ/モバイル UI。<br>ローカルでもリモートでも使え、アクティブなプロジェクトとセッションをどこからでも閲覧できます。</p>
|
||||
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>、<a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>、<a href="https://developers.openai.com/codex">Codex</a> のためのデスクトップ/モバイル UI。<br>ローカルでもリモートでも使え、アクティブなプロジェクトとセッションをどこからでも閲覧できます。</p>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
@@ -43,7 +43,7 @@
|
||||
<h3>CLI 選択</h3>
|
||||
<img src="public/screenshots/cli-selection.png" alt="CLI 選択" width="400">
|
||||
<br>
|
||||
<em>Claude Code、、Cursor CLI、Codex から選択</em>
|
||||
<em>Claude Code、Cursor CLI、Codex から選択</em>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -99,7 +99,7 @@ cloudcli
|
||||
npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
|
||||
```
|
||||
|
||||
Claude Code、Codex、 に対応。詳細は[サンドボックスのドキュメント](docker/)をご覧ください。
|
||||
Claude Code、Codex に対応。詳細は[サンドボックスのドキュメント](docker/)をご覧ください。
|
||||
|
||||
---
|
||||
|
||||
@@ -115,7 +115,7 @@ CloudCLI UI は、CloudCLI Cloud を支えるオープンソースの UI レイ
|
||||
| **マシンの稼働継続** | はい | いいえ |
|
||||
| **モバイルアクセス** | 同一ネットワーク内の任意のブラウザ | 任意のデバイス(ネイティブアプリも準備中) |
|
||||
| **利用可能なセッション** | `~/.claude` から全セッションを自動検出 | クラウド環境内の全セッション |
|
||||
| **対応エージェント** | Claude Code、Cursor CLI、Codex、 | Claude Code、Cursor CLI、Codex、 |
|
||||
| **対応エージェント** | Claude Code、Cursor CLI、Codex | Claude Code、Cursor CLI、Codex |
|
||||
| **ファイルエクスプローラとGit** | はい(UI に内蔵) | はい(UI に内蔵) |
|
||||
| **MCP設定** | UI で管理し、ローカルの `~/.claude` 設定と同期 | UI で管理 |
|
||||
| **IDEアクセス** | ローカル IDE | クラウド環境に接続された任意の IDE |
|
||||
@@ -160,7 +160,7 @@ CloudCLI にはプラグインシステムがあり、独自のフロントエ
|
||||
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | 現在のプロジェクトについて、ファイル数、コード行数、ファイル種別の内訳、最大ファイル、最近変更されたファイルを表示 |
|
||||
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | 複数タブに対応した本格的な xterm.js ターミナル |
|
||||
| **[Claude Watch](https://github.com/satsuki19980613/cloudcli-claude-watch)** | 長時間実行中の Claude Code セッションのハングを監視し、プロセス操作を提供 |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | ワークスペース単位のスケジュール済みプロンプトを作成し、Codex、Claude Code、 などのローカル CLI で実行 |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | ワークスペース単位のスケジュール済みプロンプトを作成し、Codex、Claude Code などのローカル CLI で実行 |
|
||||
| **[PRISM CloudCLI](https://github.com/jakeefr/cloudcli-plugin-prism)** | CloudCLI 内で Claude Code のセッション分析を行い、トークン消費の可視化も提供 |
|
||||
| **[Sessions](https://github.com/strykereye2/cloudcli-plugin-session-manager)** | アクティブな Claude Code セッションを表示、管理、終了 |
|
||||
| **[Token Cost Calculator](https://github.com/NightmareAway/cloudcli-plugin-token-cost-calculator)** | モデル価格とトークン使用量から API コストを計算し、モデル価格プリセットにも対応 |
|
||||
@@ -185,7 +185,7 @@ CloudCLI UI と CloudCLI Cloud は、Claude Code の横に別物として存在
|
||||
|
||||
- **すべてのセッションにアクセス** — CloudCLI UI は `~/.claude` フォルダのすべてのセッションを自動検出します。Remote Control は、Claude モバイルアプリで利用可能にするため、1つのアクティブセッションだけを公開します。
|
||||
- **設定はあなたの設定** — CloudCLI UI で変更した MCP サーバー、ツール権限、プロジェクト構成は、Claude Code の設定に直接書き込まれて即座に反映され、その逆(Claude Code での変更が UI に反映)も同様です。
|
||||
- **対応エージェントがさらに充実** — Claude Code に加えて Cursor CLI、Codex、 にも対応しています。
|
||||
- **対応エージェントがさらに充実** — Claude Code に加えて Cursor CLI、Codex にも対応しています。
|
||||
- **チャット窓だけではない完全な UI** — ファイルエクスプローラー、Git 統合、MCP 管理、シェル端末などがすべて組み込まれています。
|
||||
- **CloudCLI Cloud はクラウド上で稼働** — ノートパソコンを閉じてもエージェントは動き続けます。監視が要る端末も、スリープ防止も不要です。
|
||||
|
||||
@@ -194,7 +194,7 @@ CloudCLI UI と CloudCLI Cloud は、Claude Code の横に別物として存在
|
||||
<details>
|
||||
<summary>AI のサブスクリプションは別途支払いが必要ですか?</summary>
|
||||
|
||||
はい。CloudCLI は環境を提供するものであり、AI は含まれません。Claude、Cursor、Codex、または のサブスクリプションはご自身でご用意ください。CloudCLI Cloud のホスティング環境はそれに加えて月額 €7 から提供されます。
|
||||
はい。CloudCLI は環境を提供するものであり、AI は含まれません。Claude、Cursor、Codex のいずれかのサブスクリプションはご自身でご用意ください。CloudCLI Cloud のホスティング環境はそれに加えて月額 €7 から提供されます。
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
12
README.ru.md
12
README.ru.md
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
|
||||
<h1>Cloud CLI (aka Claude Code UI)</h1>
|
||||
<p>Десктопный и мобильный UI для <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>, <a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>, <a href="https://developers.openai.com/codex">Codex</a> и .<br>Используйте локально или удалённо, чтобы просматривать активные проекты и сессии отовсюду.</p>
|
||||
<p>Десктопный и мобильный UI для <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>, <a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>, <a href="https://developers.openai.com/codex">Codex</a>.<br>Используйте локально или удалённо, чтобы просматривать активные проекты и сессии отовсюду.</p>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
@@ -62,7 +62,7 @@
|
||||
- **Управление сессиями** - возобновляйте диалоги, управляйте несколькими сессиями и отслеживайте историю
|
||||
- **Система плагинов** - расширяйте CloudCLI кастомными плагинами — добавляйте новые вкладки, бэкенд-сервисы и интеграции. [Создать свой →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
|
||||
- **Интеграция с TaskMaster AI** *(опционально)* - продвинутое управление проектами с планированием задач на базе AI, разбором PRD и автоматизацией workflow
|
||||
- **Совместимость с моделями** - работает с семействами моделей Claude, GPT и (полный список поддерживаемых моделей доступен через `GET /api/providers/:provider/models`)
|
||||
- **Совместимость с моделями** - работает с семействами моделей Claude и GPT (полный список поддерживаемых моделей доступен через `GET /api/providers/:provider/models`)
|
||||
|
||||
|
||||
## Быстрый старт
|
||||
@@ -103,7 +103,7 @@ cloudcli
|
||||
npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
|
||||
```
|
||||
|
||||
Поддерживаются Claude Code, Codex и. Подробнее в [документации sandbox](docker/).
|
||||
Поддерживаются Claude Code и Codex. Подробнее в [документации sandbox](docker/).
|
||||
|
||||
---
|
||||
|
||||
@@ -166,7 +166,7 @@ CloudCLI UI — это open source UI-слой, на котором постро
|
||||
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Показывает количество файлов, строки кода, разбивку по типам файлов, самые большие файлы и недавно изменённые файлы для текущего проекта |
|
||||
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Полноценный терминал xterm.js с поддержкой нескольких вкладок |
|
||||
| **[Claude Watch](https://github.com/satsuki19980613/cloudcli-claude-watch)** | Отслеживает зависания долгих сессий Claude Code и предоставляет управление процессами |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | Создаёт запланированные промпты для рабочей области и запускает их через локальную CLI, например Codex, Claude Code или |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | Создаёт запланированные промпты для рабочей области и запускает их через локальную CLI, например Codex или Claude Code |
|
||||
| **[PRISM CloudCLI](https://github.com/jakeefr/cloudcli-plugin-prism)** | Аналитика сессий Claude Code внутри CloudCLI, включая видимость расхода токенов |
|
||||
| **[Sessions](https://github.com/strykereye2/cloudcli-plugin-session-manager)** | Просмотр, управление и завершение активных сессий Claude Code |
|
||||
| **[Token Cost Calculator](https://github.com/NightmareAway/cloudcli-plugin-token-cost-calculator)** | Расчёт стоимости API по ценам моделей и использованию токенов, с поддержкой пресетов цен |
|
||||
@@ -193,7 +193,7 @@ CloudCLI UI и CloudCLI Cloud расширяют Claude Code, а не работ
|
||||
|
||||
- **Все ваши сессии, а не одна** — CloudCLI UI автоматически находит каждую сессию из папки `~/.claude`. Remote Control предоставляет только одну активную сессию, чтобы сделать её доступной в мобильном приложении Claude.
|
||||
- **Ваши настройки — это ваши настройки** — MCP-серверы, права инструментов и конфигурация проекта, изменённые в CloudCLI UI, записываются напрямую в конфиг Claude Code и вступают в силу сразу же, и наоборот.
|
||||
- **Работает с большим числом агентов** — Claude Code, Cursor CLI, Codex и, а не только Claude Code.
|
||||
- **Работает с большим числом агентов** — Claude Code, Cursor CLI и Codex, а не только Claude Code.
|
||||
- **Полноценный UI, а не просто окно чата** — проводник файлов, Git-интеграция, управление MCP и shell-терминал — всё встроено.
|
||||
- **CloudCLI Cloud работает в облаке** — закройте ноутбук, и агент продолжит работать. Не нужно следить за терминалом и держать машину постоянно активной.
|
||||
|
||||
@@ -202,7 +202,7 @@ CloudCLI UI и CloudCLI Cloud расширяют Claude Code, а не работ
|
||||
<details>
|
||||
<summary>Нужно ли отдельно платить за AI-подписку?</summary>
|
||||
|
||||
Да. CloudCLI предоставляет среду, а не сам AI. Вы приносите свою подписку Claude, Cursor, Codex или . CloudCLI Cloud начинается от €7/месяц за хостируемую среду поверх этого.
|
||||
Да. CloudCLI предоставляет среду, а не сам AI. Вы приносите свою подписку Claude, Cursor или Codex. CloudCLI Cloud начинается от €7/месяц за хостируемую среду поверх этого.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
12
README.tr.md
12
README.tr.md
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
|
||||
<h1>Cloud CLI (Claude Code UI olarak da bilinir)</h1>
|
||||
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>, <a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>, <a href="https://developers.openai.com/codex">Codex</a> ve için masaüstü ve mobil arayüz.<br>Yerel ya da uzaktan kullanarak aktif projelerine ve oturumlarına her yerden erişebilirsin.</p>
|
||||
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>, <a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>, <a href="https://developers.openai.com/codex">Codex</a> için masaüstü ve mobil arayüz.<br>Yerel ya da uzaktan kullanarak aktif projelerine ve oturumlarına her yerden erişebilirsin.</p>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
@@ -62,7 +62,7 @@
|
||||
- **Oturum Yönetimi** — Konuşmalara devam et, birden fazla oturumu yönet ve geçmişi takip et
|
||||
- **Eklenti Sistemi** — CloudCLI'ı özel eklentilerle genişlet: yeni sekmeler, arka uç servisleri ve entegrasyonlar ekle. [Kendi eklentini yaz →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
|
||||
- **TaskMaster AI Entegrasyonu** *(İsteğe Bağlı)* — AI destekli görev planlama, PRD ayrıştırma ve iş akışı otomasyonu ile gelişmiş proje yönetimi
|
||||
- **Model Uyumluluğu** — Claude, GPT ve model aileleriyle çalışır (desteklenen tüm modeller için `GET /api/providers/:provider/models` API'sine bak)
|
||||
- **Model Uyumluluğu** — Claude ve GPT model aileleriyle çalışır (desteklenen tüm modeller için `GET /api/providers/:provider/models` API'sine bak)
|
||||
|
||||
|
||||
## Hızlı Başlangıç
|
||||
@@ -103,7 +103,7 @@ Ajanları hipervizör seviyesinde izolasyonlu sandbox'larda çalıştır. Varsay
|
||||
npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
|
||||
```
|
||||
|
||||
Claude Code, Codex ve destekler. Kurulum ve gelişmiş seçenekler için [sandbox dokümantasyonuna](docker/) bak.
|
||||
Claude Code ve Codex'i destekler. Kurulum ve gelişmiş seçenekler için [sandbox dokümantasyonuna](docker/) bak.
|
||||
|
||||
|
||||
---
|
||||
@@ -165,7 +165,7 @@ CloudCLI, kendi frontend UI'sı ve isteğe bağlı Node.js arka ucu olan özel s
|
||||
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Mevcut projen için dosya sayıları, kod satırları, dosya türü dağılımı, en büyük dosyalar ve son değiştirilen dosyaları gösterir |
|
||||
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Çoklu sekme destekli tam xterm.js terminali |
|
||||
| **[Claude Watch](https://github.com/satsuki19980613/cloudcli-claude-watch)** | Uzun süren Claude Code oturumlarını takılmalara karşı izler ve süreç kontrolleri sunar |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | Çalışma alanı kapsamlı zamanlanmış prompt'lar oluşturur ve bunları Codex, Claude Code veya gibi yerel CLI'larla çalıştırır |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | Çalışma alanı kapsamlı zamanlanmış prompt'lar oluşturur ve bunları Codex veya Claude Code gibi yerel CLI'larla çalıştırır |
|
||||
| **[PRISM CloudCLI](https://github.com/jakeefr/cloudcli-plugin-prism)** | CloudCLI içinde Claude Code oturum zekası ve token tüketimi görünürlüğü sağlar |
|
||||
| **[Sessions](https://github.com/strykereye2/cloudcli-plugin-session-manager)** | Aktif Claude Code oturumlarını görüntülemeni, yönetmeni ve sonlandırmanı sağlar |
|
||||
| **[Token Cost Calculator](https://github.com/NightmareAway/cloudcli-plugin-token-cost-calculator)** | Model fiyatları ve token kullanımından API maliyetlerini hesaplar; model fiyatı hazır ayarlarını destekler |
|
||||
@@ -192,7 +192,7 @@ Pratikte bu ne demek:
|
||||
|
||||
- **Tek oturum değil, tüm oturumların** — CloudCLI UI, `~/.claude` klasöründeki her oturumu otomatik keşfeder. Remote Control yalnızca tek aktif oturumu Claude mobil uygulamasına açar.
|
||||
- **Ayarların sana ait** — UI'da değiştirdiğin MCP sunucuları, araç izinleri ve proje yapılandırması doğrudan Claude Code yapılandırmana yazılır ve anında etkili olur; tersi de geçerli.
|
||||
- **Daha fazla ajanla çalışır** — Sadece Claude Code değil; Cursor CLI, Codex ve de.
|
||||
- **Daha fazla ajanla çalışır** — Sadece Claude Code değil; Cursor CLI ve Codex de.
|
||||
- **Sadece sohbet penceresi değil, tam UI** — dosya gezgini, Git entegrasyonu, MCP yönetimi ve shell terminali hepsi yerleşik.
|
||||
- **CloudCLI Cloud bulutta çalışır** — laptop'unu kapat, ajan çalışmaya devam eder. Beklemen gereken terminal yok, uyanık tutman gereken makine yok.
|
||||
|
||||
@@ -201,7 +201,7 @@ Pratikte bu ne demek:
|
||||
<details>
|
||||
<summary>AI aboneliği için ayrıca ödeme yapmam gerekiyor mu?</summary>
|
||||
|
||||
Evet. CloudCLI AI'yi değil, ortamı sağlar. Kendi Claude, Cursor, Codex veya aboneliğini getirirsin. CloudCLI Cloud, barındırılan ortam için aylık 7 €'dan başlar — bunun üzerine eklenir.
|
||||
Evet. CloudCLI AI'yi değil, ortamı sağlar. Kendi Claude, Cursor veya Codex aboneliğini getirirsin. CloudCLI Cloud, barındırılan ortam için aylık 7 €'dan başlar — bunun üzerine eklenir.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
|
||||
<h1>Cloud CLI(又名 Claude Code UI)</h1>
|
||||
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>、<a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>、<a href="https://developers.openai.com/codex">Codex</a> 和 的桌面和移动端 UI。可在本地或远程使用,从任何地方查看激活的项目与会话。</p>
|
||||
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>、<a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>、<a href="https://developers.openai.com/codex">Codex</a> 的桌面和移动端 UI。可在本地或远程使用,从任何地方查看激活的项目与会话。</p>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
@@ -43,7 +43,7 @@
|
||||
<h3>CLI 选择</h3>
|
||||
<img src="public/screenshots/cli-selection.png" alt="CLI 选择" width="400">
|
||||
<br>
|
||||
<em>在 Claude Code、、Cursor CLI 与 Codex 之间进行选择</em>
|
||||
<em>在 Claude Code、Cursor CLI 与 Codex 之间进行选择</em>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -60,7 +60,7 @@
|
||||
- **会话管理** - 恢复对话、管理多个会话并跟踪历史记录
|
||||
- **插件系统** - 通过自定义选项卡、后端服务与集成扩展 CloudCLI。 [开始构建 →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
|
||||
- **TaskMaster AI 集成** *(可选)* - 结合 AI 任务规划、PRD 分析与工作流自动化,实现高级项目管理
|
||||
- **模型兼容性** - 支持 Claude、GPT、 模型家族(完整支持列表可通过 `GET /api/providers/:provider/models` 接口获取)
|
||||
- **模型兼容性** - 支持 Claude、GPT 模型家族(完整支持列表可通过 `GET /api/providers/:provider/models` 接口获取)
|
||||
|
||||
## 快速开始
|
||||
|
||||
@@ -99,7 +99,7 @@ cloudcli
|
||||
npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
|
||||
```
|
||||
|
||||
支持 Claude Code、Codex 和。详情请参阅 [沙箱文档](docker/)。
|
||||
支持 Claude Code 和 Codex。详情请参阅 [沙箱文档](docker/)。
|
||||
|
||||
---
|
||||
|
||||
@@ -115,7 +115,7 @@ CloudCLI UI 是 CloudCLI Cloud 的开源 UI 层。你可以在本地机器上自
|
||||
| **机器需保持开机吗** | 是 | 否 |
|
||||
| **移动端访问** | 网络内任意浏览器 | 任意设备(原生应用即将推出) |
|
||||
| **可用会话** | 自动发现 `~/.claude` 中的所有会话 | 云端环境内的会话 |
|
||||
| **支持的 Agents** | Claude Code、Cursor CLI、Codex、 | Claude Code、Cursor CLI、Codex、 |
|
||||
| **支持的 Agents** | Claude Code、Cursor CLI、Codex | Claude Code、Cursor CLI、Codex |
|
||||
| **文件浏览与 Git** | 内置于 UI | 内置于 UI |
|
||||
| **MCP 配置** | UI 管理,与本地 `~/.claude` 配置同步 | UI 管理 |
|
||||
| **IDE 访问** | 本地 IDE | 任何连接到云环境的 IDE |
|
||||
@@ -160,7 +160,7 @@ CloudCLI 配备插件系统,允许你添加带自定义前端 UI 和可选 Nod
|
||||
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | 展示当前项目的文件数、代码行数、文件类型分布、最大文件以及最近修改的文件 |
|
||||
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | 支持多标签页的完整 xterm.js 终端 |
|
||||
| **[Claude Watch](https://github.com/satsuki19980613/cloudcli-claude-watch)** | 监控长时间运行的 Claude Code 会话是否卡住,并提供进程控制 |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | 创建工作区范围的定时提示词,并通过 Codex、Claude Code 或 等本地 CLI 执行 |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | 创建工作区范围的定时提示词,并通过 Codex、Claude Code 等本地 CLI 执行 |
|
||||
| **[PRISM CloudCLI](https://github.com/jakeefr/cloudcli-plugin-prism)** | 在 CloudCLI 中提供 Claude Code 会话智能分析,包括 token 消耗可视化 |
|
||||
| **[Sessions](https://github.com/strykereye2/cloudcli-plugin-session-manager)** | 查看、管理并终止活动的 Claude Code 会话 |
|
||||
| **[Token Cost Calculator](https://github.com/NightmareAway/cloudcli-plugin-token-cost-calculator)** | 根据模型价格和 token 用量计算 API 成本,并支持模型价格预设 |
|
||||
@@ -186,7 +186,7 @@ CloudCLI UI 与 CloudCLI Cloud 是对 Claude Code 的扩展,而非旁观 — M
|
||||
|
||||
- **覆盖全部会话** — CloudCLI UI 会自动扫描 `~/.claude` 文件夹中的每个会话。Remote Control 只暴露当前活动的会话。
|
||||
- **设置统一** — 在 CloudCLI UI 中修改的 MCP、工具权限等设置会立即写入 Claude Code。
|
||||
- **支持更多 Agents** — Claude Code、Cursor CLI、Codex、。
|
||||
- **支持更多 Agents** — Claude Code、Cursor CLI、Codex。
|
||||
- **完整 UI** — 除了聊天界面,还包括文件浏览器、Git 集成、MCP 管理和 Shell 终端。
|
||||
- **CloudCLI Cloud 保持运行于云端** — 关闭本地设备也不会中断代理运行,无需监控终端。
|
||||
|
||||
@@ -195,7 +195,7 @@ CloudCLI UI 与 CloudCLI Cloud 是对 Claude Code 的扩展,而非旁观 — M
|
||||
<details>
|
||||
<summary>需要额外购买 AI 订阅吗?</summary>
|
||||
|
||||
需要。CloudCLI 只提供环境。你仍需自行获取 Claude、Cursor、Codex 或 订阅。CloudCLI Cloud 从 €7/月起提供托管环境。
|
||||
需要。CloudCLI 只提供环境。你仍需自行获取 Claude、Cursor 或 Codex 订阅。CloudCLI Cloud 从 €7/月起提供托管环境。
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
|
||||
<h1>Cloud CLI(又名 Claude Code UI)</h1>
|
||||
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>、<a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>、<a href="https://developers.openai.com/codex">Codex</a> 和 的桌面和行動裝置 UI。可在本機或遠端使用,從任何地方查看您的專案與工作階段。</p>
|
||||
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>、<a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>、<a href="https://developers.openai.com/codex">Codex</a> 的桌面和行動裝置 UI。可在本機或遠端使用,從任何地方查看您的專案與工作階段。</p>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
@@ -43,7 +43,7 @@
|
||||
<h3>CLI 選擇</h3>
|
||||
<img src="public/screenshots/cli-selection.png" alt="CLI 選擇" width="400">
|
||||
<br>
|
||||
<em>在 Claude Code、、Cursor CLI 與 Codex 之間進行選擇</em>
|
||||
<em>在 Claude Code、Cursor CLI 與 Codex 之間進行選擇</em>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -60,7 +60,7 @@
|
||||
- **工作階段管理** — 恢復對話、管理多個工作階段並追蹤歷史紀錄
|
||||
- **外掛系統** — 透過自訂分頁、後端服務與整合來擴充 CloudCLI。[開始建構 →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
|
||||
- **TaskMaster AI 整合** *(選用)* — 結合 AI 任務規劃、PRD 分析與工作流程自動化,實現進階專案管理
|
||||
- **模型相容性** — 支援 Claude、GPT、 模型家族(完整支援列表可透過 `GET /api/providers/:provider/models` 介面取得)
|
||||
- **模型相容性** — 支援 Claude、GPT 模型家族(完整支援列表可透過 `GET /api/providers/:provider/models` 介面取得)
|
||||
|
||||
## 快速開始
|
||||
|
||||
@@ -99,7 +99,7 @@ cloudcli
|
||||
npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
|
||||
```
|
||||
|
||||
支援 Claude Code、Codex 和。詳情請參閱[沙箱文件](docker/)。
|
||||
支援 Claude Code 和 Codex。詳情請參閱[沙箱文件](docker/)。
|
||||
|
||||
---
|
||||
|
||||
@@ -115,7 +115,7 @@ CloudCLI UI 是 CloudCLI Cloud 的開源 UI 層。你可以在本機上自架它
|
||||
| **機器需保持開機嗎** | 是 | 否 |
|
||||
| **行動裝置存取** | 網路內任意瀏覽器 | 任意裝置(原生應用程式即將推出) |
|
||||
| **可用工作階段** | 自動發現 `~/.claude` 中的所有工作階段 | 雲端環境內的工作階段 |
|
||||
| **支援的 Agents** | Claude Code、Cursor CLI、Codex、 | Claude Code、Cursor CLI、Codex、 |
|
||||
| **支援的 Agents** | Claude Code、Cursor CLI、Codex | Claude Code、Cursor CLI、Codex |
|
||||
| **檔案瀏覽與 Git** | 內建於 UI | 內建於 UI |
|
||||
| **MCP 設定** | UI 管理,與本機 `~/.claude` 設定同步 | UI 管理 |
|
||||
| **IDE 存取** | 本機 IDE | 任何連線到雲端環境的 IDE |
|
||||
@@ -160,7 +160,7 @@ CloudCLI 配備外掛系統,允許你新增帶有自訂前端 UI 和選用 Nod
|
||||
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | 展示目前專案的檔案數、程式碼行數、檔案類型分佈、最大檔案以及最近修改的檔案 |
|
||||
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | 支援多分頁的完整 xterm.js 終端機 |
|
||||
| **[Claude Watch](https://github.com/satsuki19980613/cloudcli-claude-watch)** | 監控長時間執行的 Claude Code 工作階段是否卡住,並提供程序控制 |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | 建立工作區範圍的排程提示詞,並透過 Codex、Claude Code 或 等本機 CLI 執行 |
|
||||
| **[CloudCLI Scheduler](https://github.com/grostim/cloudcli-cron)** | 建立工作區範圍的排程提示詞,並透過 Codex、Claude Code 等本機 CLI 執行 |
|
||||
| **[PRISM CloudCLI](https://github.com/jakeefr/cloudcli-plugin-prism)** | 在 CloudCLI 中提供 Claude Code 工作階段智慧分析,包括 token 消耗可視化 |
|
||||
| **[Sessions](https://github.com/strykereye2/cloudcli-plugin-session-manager)** | 檢視、管理並終止作用中的 Claude Code 工作階段 |
|
||||
| **[Token Cost Calculator](https://github.com/NightmareAway/cloudcli-plugin-token-cost-calculator)** | 根據模型價格與 token 用量計算 API 成本,並支援模型價格預設 |
|
||||
@@ -186,7 +186,7 @@ CloudCLI UI 與 CloudCLI Cloud 是對 Claude Code 的擴充,而非旁觀 — M
|
||||
|
||||
- **涵蓋全部工作階段** — CloudCLI UI 會自動掃描 `~/.claude` 資料夾中的每個工作階段。Remote Control 只暴露目前活動的工作階段。
|
||||
- **設定統一** — 在 CloudCLI UI 中修改的 MCP、工具權限等設定會立即寫入 Claude Code。
|
||||
- **支援更多 Agents** — Claude Code、Cursor CLI、Codex、。
|
||||
- **支援更多 Agents** — Claude Code、Cursor CLI、Codex。
|
||||
- **完整 UI** — 除了聊天介面,還包括檔案瀏覽器、Git 整合、MCP 管理和 Shell 終端機。
|
||||
- **CloudCLI Cloud 持續運作於雲端** — 關閉本機裝置也不會中斷代理執行,無需監控終端機。
|
||||
|
||||
@@ -195,7 +195,7 @@ CloudCLI UI 與 CloudCLI Cloud 是對 Claude Code 的擴充,而非旁觀 — M
|
||||
<details>
|
||||
<summary>需要額外購買 AI 訂閱嗎?</summary>
|
||||
|
||||
需要。CloudCLI 只提供環境。你仍需自行取得 Claude、Cursor、Codex 或 訂閱。CloudCLI Cloud 從 €7/月起提供託管環境。
|
||||
需要。CloudCLI 只提供環境。你仍需自行取得 Claude、Cursor 或 Codex 訂閱。CloudCLI Cloud 從 €7/月起提供託管環境。
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -79,7 +79,17 @@ router.get('/images/:filename', async (req, res) => {
|
||||
return res.status(404).json({ error: 'Asset not found' });
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', mime.lookup(resolved) || 'application/octet-stream');
|
||||
const contentType = mime.lookup(resolved) || 'application/octet-stream';
|
||||
res.setHeader('Content-Type', contentType);
|
||||
// Stored-XSS hardening: never let the browser sniff a different type, and
|
||||
// force SVGs (which can carry scripts when rendered as a document) to
|
||||
// download instead of rendering inline. The chat UI is unaffected — it
|
||||
// fetches assets as blobs and shows them through <img>, where SVG scripts
|
||||
// never execute.
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
if (contentType === 'image/svg+xml') {
|
||||
res.setHeader('Content-Disposition', 'attachment');
|
||||
}
|
||||
const fileStream = fsSync.createReadStream(resolved);
|
||||
fileStream.pipe(res);
|
||||
fileStream.on('error', (error) => {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import type { WebSocket } from 'ws';
|
||||
|
||||
import { sessionsDb } from '@/modules/database/index.js';
|
||||
import { chatRunRegistry } from '@/modules/websocket/services/chat-run-registry.service.js';
|
||||
import { connectedClients, WS_OPEN_STATE } from '@/modules/websocket/services/websocket-state.service.js';
|
||||
import { getGlobalImageAssetsDir, normalizeImageDescriptors } from '@/shared/image-attachments.js';
|
||||
import type {
|
||||
AnyRecord,
|
||||
AuthenticatedWebSocketRequest,
|
||||
@@ -10,6 +13,37 @@ import type {
|
||||
} from '@/shared/types.js';
|
||||
import { parseIncomingJsonObject } from '@/shared/utils.js';
|
||||
|
||||
/**
|
||||
* Trust boundary for client-supplied image attachments: chat.send options come
|
||||
* straight from the browser, and the provider runtimes read the referenced
|
||||
* files off disk (Claude base64-encodes them into the prompt). Only images
|
||||
* that live directly inside the global upload store (`~/.cloudcli/assets`,
|
||||
* where POST /api/assets/images puts them) are allowed through — anything
|
||||
* else (absolute paths elsewhere, traversal, subdirectories) is dropped.
|
||||
*
|
||||
* Exported for tests; `assetsRootOverride` exists only for them.
|
||||
*/
|
||||
export function filterImagesToUploadStore(images: unknown, assetsRootOverride?: string): AnyRecord[] {
|
||||
const assetsRoot = path.resolve(assetsRootOverride ?? getGlobalImageAssetsDir());
|
||||
|
||||
return normalizeImageDescriptors(images).filter((descriptor) => {
|
||||
// Relative paths are anchored in the store; absolute ones must already be in it.
|
||||
const resolved = path.resolve(assetsRoot, descriptor.path);
|
||||
const relative = path.relative(assetsRoot, resolved);
|
||||
const isDirectChild =
|
||||
relative.length > 0 &&
|
||||
!relative.startsWith('..') &&
|
||||
!path.isAbsolute(relative) &&
|
||||
!relative.includes(path.sep) &&
|
||||
!relative.includes('/');
|
||||
|
||||
if (!isDirectChild) {
|
||||
console.warn(`[Chat] Dropping image outside the upload store: ${descriptor.path}`);
|
||||
}
|
||||
return isDirectChild;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* One provider runtime entry point. All five runtimes share this signature,
|
||||
* which lets the chat handler dispatch through a provider-keyed map instead
|
||||
@@ -161,6 +195,9 @@ async function handleChatSend(
|
||||
// gateway writer captures and maps back to the app session id.
|
||||
const runtimeOptions: AnyRecord = {
|
||||
...clientOptions,
|
||||
// Image attachments are re-validated server-side: only files inside the
|
||||
// global upload store may reach the provider runtimes' file reads.
|
||||
images: filterImagesToUploadStore(clientOptions.images),
|
||||
sessionId: session.provider_session_id ?? undefined,
|
||||
resume: Boolean(session.provider_session_id),
|
||||
cwd: clientOptions.cwd ?? session.project_path ?? undefined,
|
||||
|
||||
44
server/modules/websocket/tests/chat-image-filter.test.ts
Normal file
44
server/modules/websocket/tests/chat-image-filter.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import { filterImagesToUploadStore } from '@/modules/websocket/services/chat-websocket.service.js';
|
||||
|
||||
const STORE = path.join(os.tmpdir(), 'cloudcli-assets-store');
|
||||
|
||||
test('images inside the upload store pass through', () => {
|
||||
const inside = path.join(STORE, 'shot.png');
|
||||
const result = filterImagesToUploadStore(
|
||||
[{ path: inside, name: 'shot.png', mimeType: 'image/png' }],
|
||||
STORE,
|
||||
);
|
||||
assert.equal(result.length, 1);
|
||||
assert.equal(result[0].path, inside);
|
||||
});
|
||||
|
||||
test('bare filenames are anchored inside the store', () => {
|
||||
const result = filterImagesToUploadStore(['shot.png'], STORE);
|
||||
assert.equal(result.length, 1);
|
||||
});
|
||||
|
||||
test('paths outside the store, traversal, and subdirs are dropped', () => {
|
||||
const result = filterImagesToUploadStore(
|
||||
[
|
||||
{ path: 'C:/Users/victim/.ssh/id_rsa' },
|
||||
{ path: '/etc/passwd' },
|
||||
{ path: '../outside.png' },
|
||||
{ path: path.join(STORE, '..', 'escaped.png') },
|
||||
{ path: path.join(STORE, 'nested', 'deep.png') },
|
||||
{ path: STORE }, // the store folder itself is not a file
|
||||
],
|
||||
STORE,
|
||||
);
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
|
||||
test('malformed payloads yield no images', () => {
|
||||
assert.deepEqual(filterImagesToUploadStore(undefined, STORE), []);
|
||||
assert.deepEqual(filterImagesToUploadStore('nope', STORE), []);
|
||||
assert.deepEqual(filterImagesToUploadStore([{ name: 'no-path' }, 42], STORE), []);
|
||||
});
|
||||
@@ -94,7 +94,7 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, s
|
||||
projectId={selectedProject?.projectId}
|
||||
/>
|
||||
)}
|
||||
{(userCopyContent.trim().length > 0 || !message.images?.length) && (
|
||||
{userCopyContent.trim().length > 0 || !message.images?.length ? (
|
||||
<div className="group max-w-full rounded-2xl rounded-br-md bg-blue-600 px-3 py-2 text-white shadow-sm sm:px-4">
|
||||
<div dir="auto" className="whitespace-pre-wrap break-words font-serif text-sm">
|
||||
{message.content}
|
||||
@@ -106,6 +106,11 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, s
|
||||
<span>{formattedTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* Image-only turn: no text bubble, but the timestamp still shows */
|
||||
<div className="flex items-center justify-end gap-1 text-xs text-muted-foreground">
|
||||
<span>{formattedTime}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isGrouped && (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GitBranch, GitCommit, RefreshCw } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { ConfirmationRequest, FileStatusCode, GitDiffMap, GitStatusResponse } from '../../types/types';
|
||||
import { getAllChangedFiles, hasChangedFiles } from '../../utils/gitPanelUtils';
|
||||
import CommitComposer from './CommitComposer';
|
||||
@@ -49,20 +49,40 @@ export default function ChangesView({
|
||||
}: ChangesViewProps) {
|
||||
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set());
|
||||
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
|
||||
// Stage/unstage calls in flight or queued. While > 0, status refreshes must
|
||||
// not overwrite the optimistic selection with a snapshot that predates the
|
||||
// later clicks.
|
||||
const [pendingStageOps, setPendingStageOps] = useState(0);
|
||||
// Serializes stage/unstage requests so rapid toggles cannot interleave on
|
||||
// the server or resolve out of order.
|
||||
const stageOpQueueRef = useRef<Promise<unknown>>(Promise.resolve());
|
||||
|
||||
const changedFiles = useMemo(() => getAllChangedFiles(gitStatus), [gitStatus]);
|
||||
const hasExpandedFiles = expandedFiles.size > 0;
|
||||
|
||||
const enqueueStageOp = useCallback((operation: () => Promise<unknown>) => {
|
||||
setPendingStageOps((count) => count + 1);
|
||||
stageOpQueueRef.current = stageOpQueueRef.current
|
||||
.catch(() => {}) // a failed op must not block the queue
|
||||
.then(operation)
|
||||
.finally(() => setPendingStageOps((count) => count - 1));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!gitStatus || gitStatus.error) {
|
||||
setSelectedFiles(new Set());
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingStageOps > 0) {
|
||||
return; // keep the optimistic state until the queued ops settle
|
||||
}
|
||||
|
||||
// The Staged section mirrors the real git index reported by /status, so
|
||||
// files staged outside the app (VSCode, terminal) show up here too.
|
||||
// files staged outside the app (VSCode, terminal) show up here too. Also
|
||||
// re-runs when the queue drains, syncing to the final refreshed status.
|
||||
setSelectedFiles(new Set(gitStatus.staged ?? []));
|
||||
}, [gitStatus]);
|
||||
}, [gitStatus, pendingStageOps]);
|
||||
|
||||
useEffect(() => {
|
||||
onExpandedFilesChange(hasExpandedFiles);
|
||||
@@ -87,8 +107,8 @@ export default function ChangesView({
|
||||
}, []);
|
||||
|
||||
// Staging is real: every toggle runs git add / git reset through the API.
|
||||
// The set is flipped optimistically; the status refresh triggered by the
|
||||
// API call re-syncs it from the actual index afterwards.
|
||||
// The set is flipped optimistically; the queued API call keeps the git
|
||||
// index in sync and the final status refresh re-syncs once the queue drains.
|
||||
const toggleFileSelected = useCallback(
|
||||
(filePath: string) => {
|
||||
const isStaged = selectedFiles.has(filePath);
|
||||
@@ -101,9 +121,9 @@ export default function ChangesView({
|
||||
}
|
||||
return next;
|
||||
});
|
||||
void (isStaged ? onUnstageFiles([filePath]) : onStageFiles([filePath]));
|
||||
enqueueStageOp(() => (isStaged ? onUnstageFiles([filePath]) : onStageFiles([filePath])));
|
||||
},
|
||||
[onStageFiles, onUnstageFiles, selectedFiles],
|
||||
[enqueueStageOp, onStageFiles, onUnstageFiles, selectedFiles],
|
||||
);
|
||||
|
||||
const requestFileAction = useCallback(
|
||||
@@ -209,7 +229,7 @@ export default function ChangesView({
|
||||
onClick={() => {
|
||||
const filesToUnstage = Array.from(selectedFiles);
|
||||
setSelectedFiles(new Set());
|
||||
void onUnstageFiles(filesToUnstage);
|
||||
enqueueStageOp(() => onUnstageFiles(filesToUnstage));
|
||||
}}
|
||||
className="text-xs text-primary transition-colors hover:text-primary/80"
|
||||
>
|
||||
@@ -246,7 +266,7 @@ export default function ChangesView({
|
||||
onClick={() => {
|
||||
const filesToStage = Array.from(unstagedFiles);
|
||||
setSelectedFiles(new Set(changedFiles));
|
||||
void onStageFiles(filesToStage);
|
||||
enqueueStageOp(() => onStageFiles(filesToStage));
|
||||
}}
|
||||
className="text-xs text-primary transition-colors hover:text-primary/80"
|
||||
>
|
||||
|
||||
@@ -62,7 +62,9 @@ export default function CommitHistoryItem({
|
||||
return parseCommitFiles(diff);
|
||||
}, [diff]);
|
||||
|
||||
const badgeColor = graphRow ? laneColor(graphRow.nodeLane) : 'var(--color-primary, #0ea5e9)';
|
||||
// Must stay a literal hex value: RefBadge derives its HEAD tint by
|
||||
// appending an alpha byte (`${color}22`), which breaks for var() strings.
|
||||
const badgeColor = graphRow ? laneColor(graphRow.nodeLane) : '#0ea5e9';
|
||||
|
||||
return (
|
||||
<div className="flex border-b border-border last:border-0">
|
||||
|
||||
@@ -810,19 +810,18 @@ export function useProjectsState({
|
||||
return;
|
||||
}
|
||||
|
||||
const projectForSession = selectedProject || projects.find((p) => (p.sessions?.length ?? 0) > 0);
|
||||
if (!projectForSession) {
|
||||
// Only the currently selected project may host the placeholder. Guessing
|
||||
// another project (e.g. "first one with sessions") could bind the URL
|
||||
// session to the wrong project — better to wait until the owning project
|
||||
// arrives in a later `projects` payload and is matched by the loop above.
|
||||
if (!selectedProject) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedProject?.projectId !== projectForSession.projectId) {
|
||||
setSelectedProject(projectForSession);
|
||||
}
|
||||
|
||||
setSelectedSession({
|
||||
id: sessionId,
|
||||
__provider: readSelectedProvider(),
|
||||
__projectId: projectForSession.projectId,
|
||||
__projectId: selectedProject.projectId,
|
||||
summary: '',
|
||||
});
|
||||
}, [sessionId, projects, selectedProject, selectedSession?.id, selectedSession?.__provider]);
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"clearSearch": "Suche leeren",
|
||||
"openCommandPalette": "Befehlspalette öffnen"
|
||||
"openCommandPalette": "Befehlspalette öffnen",
|
||||
"attentionRequiredIndicator": "Sitzung erfordert Aufmerksamkeit"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "Chat",
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"clearSearch": "Clear search",
|
||||
"openCommandPalette": "Open command palette"
|
||||
"openCommandPalette": "Open command palette",
|
||||
"attentionRequiredIndicator": "Session needs attention"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "Chat",
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"clearSearch": "Effacer la recherche",
|
||||
"openCommandPalette": "Ouvrir la palette de commandes"
|
||||
"openCommandPalette": "Ouvrir la palette de commandes",
|
||||
"attentionRequiredIndicator": "La session nécessite votre attention"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "Chat",
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"save": "Salva",
|
||||
"cancel": "Annulla",
|
||||
"clearSearch": "Cancella ricerca",
|
||||
"openCommandPalette": "Apri tavolozza comandi"
|
||||
"openCommandPalette": "Apri tavolozza comandi",
|
||||
"attentionRequiredIndicator": "La sessione richiede attenzione"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "Chat",
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"activeSessionIndicator": "最近アクティブなセッション(過去10分以内)",
|
||||
"save": "保存",
|
||||
"cancel": "キャンセル",
|
||||
"openCommandPalette": "コマンドパレットを開く"
|
||||
"openCommandPalette": "コマンドパレットを開く",
|
||||
"attentionRequiredIndicator": "セッションが対応を必要としています"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "チャット",
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"activeSessionIndicator": "최근 활성 세션 (지난 10분)",
|
||||
"save": "저장",
|
||||
"cancel": "취소",
|
||||
"openCommandPalette": "명령 팔레트 열기"
|
||||
"openCommandPalette": "명령 팔레트 열기",
|
||||
"attentionRequiredIndicator": "세션에 주의가 필요합니다"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "채팅",
|
||||
@@ -128,4 +129,4 @@
|
||||
"allConversationsDeleted": "프로젝트가 사이드바에서 제거됩니다. 파일, 메모리 및 세션 데이터는 보존됩니다.",
|
||||
"cannotUndo": "나중에 프로젝트를 다시 추가할 수 있습니다."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"save": "Сохранить",
|
||||
"cancel": "Отмена",
|
||||
"clearSearch": "Очистить поиск",
|
||||
"openCommandPalette": "Открыть палитру команд"
|
||||
"openCommandPalette": "Открыть палитру команд",
|
||||
"attentionRequiredIndicator": "Сессия требует внимания"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "Чат",
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"save": "Kaydet",
|
||||
"cancel": "İptal",
|
||||
"clearSearch": "Aramayı temizle",
|
||||
"openCommandPalette": "Komut paletini aç"
|
||||
"openCommandPalette": "Komut paletini aç",
|
||||
"attentionRequiredIndicator": "Oturum ilgi bekliyor"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "Sohbet",
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"clearSearch": "清除搜索",
|
||||
"openCommandPalette": "打开命令面板"
|
||||
"openCommandPalette": "打开命令面板",
|
||||
"attentionRequiredIndicator": "会话需要处理"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "聊天",
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"save": "儲存",
|
||||
"cancel": "取消",
|
||||
"clearSearch": "清除搜尋",
|
||||
"openCommandPalette": "開啟指令面板"
|
||||
"openCommandPalette": "開啟指令面板",
|
||||
"attentionRequiredIndicator": "工作階段需要處理"
|
||||
},
|
||||
"navigation": {
|
||||
"chat": "聊天",
|
||||
|
||||
Reference in New Issue
Block a user