);
}
-export default ThinkingModeSelector;
\ No newline at end of file
+export default ThinkingModeSelector;
From 13e97e2c71254de7a60afb5495b21064c4bc4241 Mon Sep 17 00:00:00 2001
From: simosmik
Date: Tue, 14 Apr 2026 15:18:02 +0000
Subject: [PATCH 06/14] feat: adding docker sandbox environments
---
README.de.md | 11 +
README.ja.md | 11 +
README.ko.md | 14 +-
README.md | 48 ++--
README.ru.md | 13 +-
README.zh-CN.md | 14 +-
docker/README.md | 173 +++++++++-----
docker/claude-code/Dockerfile | 4 +-
docker/codex/Dockerfile | 4 +-
docker/gemini/Dockerfile | 4 +-
docker/shared/install-cloudcli.sh | 11 +-
docker/shared/start-cloudcli.sh | 9 +-
server/cli.js | 365 +++++++++++++++++++++++++++++-
13 files changed, 578 insertions(+), 103 deletions(-)
diff --git a/README.de.md b/README.de.md
index 4e163781..4e354020 100644
--- a/README.de.md
+++ b/README.de.md
@@ -76,6 +76,8 @@ Der schnellste Einstieg – keine lokale Einrichtung erforderlich. Erhalte eine
### Self-Hosted (Open Source)
+#### npm
+
CloudCLI UI sofort mit **npx** ausprobieren (erfordert **Node.js** v22+):
```bash
@@ -93,6 +95,15 @@ cloudcli
Die **[Dokumentation →](https://cloudcli.ai/docs)** enthält weitere Konfigurationsoptionen, PM2, Remote-Server-Einrichtung und mehr.
+#### Docker Sandboxes (Experimentell)
+
+Agents in isolierten Sandboxes mit Hypervisor-Isolation ausführen. Standardmäßig wird Claude Code gestartet. Erfordert die [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/).
+
+```
+npx @cloudcli-ai/cloudcli sandbox ~/my-project
+```
+
+Unterstützt Claude Code, Codex und Gemini CLI. Weitere Details in der [Sandbox-Dokumentation](docker/).
---
diff --git a/README.ja.md b/README.ja.md
index eada6dec..dfca8cda 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -72,6 +72,8 @@
### セルフホスト(オープンソース)
+#### npm
+
**npx** で今すぐ CloudCLI UI を試せます(**Node.js** v22+ が必要):
```bash
@@ -89,6 +91,15 @@ cloudcli
より詳細な設定オプション、PM2、リモートサーバー設定などについては **[ドキュメントはこちら →](https://cloudcli.ai/docs)** を参照してください。
+#### Docker Sandboxes(実験的)
+
+ハイパーバイザーレベルの分離でエージェントをサンドボックスで実行します。デフォルトでは Claude Code が起動します。[`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/) が必要です。
+
+```
+npx @cloudcli-ai/cloudcli sandbox ~/my-project
+```
+
+Claude Code、Codex、Gemini CLI に対応。詳細は[サンドボックスのドキュメント](docker/)をご覧ください。
---
diff --git a/README.ko.md b/README.ko.md
index ed8ddd9f..6cee2adf 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -72,6 +72,8 @@
### 셀프 호스트 (오픈 소스)
+#### npm
+
**npx**로 즉시 CloudCLI UI를 실행하세요 (Node.js v22+ 필요):
```bash
@@ -87,7 +89,17 @@ cloudcli
`http://localhost:3001`을 열면 기존 세션이 자동으로 발견됩니다.
-자세한 구성 옵션, PM2, 원격 서버 설정 등은 **[문서 →](https://cloudcli.ai/docs)**를 참고하세요
+자세한 구성 옵션, PM2, 원격 서버 설정 등은 **[문서 →](https://cloudcli.ai/docs)**를 참고하세요.
+
+#### Docker Sandboxes (실험적)
+
+하이퍼바이저 수준 격리로 에이전트를 샌드박스에서 실행합니다. 기본 에이전트는 Claude Code입니다. [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/)가 필요합니다.
+
+```
+npx @cloudcli-ai/cloudcli sandbox ~/my-project
+```
+
+Claude Code, Codex, Gemini CLI를 지원합니다. 자세한 내용은 [샌드박스 문서](docker/)를 참고하세요.
---
diff --git a/README.md b/README.md
index 751f1659..986505b3 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,8 @@ The fastest way to get started — no local setup required. Get a fully managed,
### Self-Hosted (Open source)
+#### npm
+
Try CloudCLI UI instantly with **npx** (requires **Node.js** v22+):
```
@@ -91,33 +93,41 @@ cloudcli
Open `http://localhost:3001` — all your existing sessions are discovered automatically.
-Visit the **[documentation →](https://cloudcli.ai/docs)** for more full configuration options, PM2, remote server setup and more
+Visit the **[documentation →](https://cloudcli.ai/docs)** for full configuration options, PM2, remote server setup and more.
+
+#### Docker Sandboxes (Experimental)
+
+Run agents in isolated sandboxes with hypervisor-level isolation. Starts Claude Code by default. Requires the [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/).
+
+```
+npx @cloudcli-ai/cloudcli sandbox ~/my-project
+```
+
+Supports Claude Code, Codex, and Gemini CLI. See the [sandbox docs](docker/) for setup and advanced options.
---
## Which option is right for you?
-CloudCLI UI is the open source UI layer that powers CloudCLI Cloud. You can self-host it on your own machine, or use CloudCLI Cloud which builds on top of it with a full managed cloud environment, team features, and deeper integrations.
+CloudCLI UI is the open source UI layer that powers CloudCLI Cloud. You can self-host it on your own machine, run it in a Docker sandbox for isolation, or use CloudCLI Cloud for a fully managed environment.
-| | CloudCLI UI (Self-hosted) | CloudCLI Cloud |
-|---|---|---|
-| **Best for** | Developers who want a full UI for local agent sessions on their own machine | Teams and developers who want agents running in the cloud, accessible from anywhere |
-| **How you access it** | Browser via `[yourip]:port` | Browser, any IDE, REST API, n8n |
-| **Setup** | `npx @cloudcli-ai/cloudcli` | No setup required |
-| **Machine needs to stay on** | Yes | No |
-| **Mobile access** | Any browser on your network | Any device, native app coming |
-| **Sessions available** | All sessions auto-discovered from `~/.claude` | All sessions within your cloud environment |
-| **Agents supported** | Claude Code, Cursor CLI, Codex, Gemini CLI | Claude Code, Cursor CLI, Codex, Gemini CLI |
-| **File explorer and Git** | Yes, built into the UI | Yes, built into the UI |
-| **MCP configuration** | Managed via UI, synced with your local `~/.claude` config | Managed via UI |
-| **IDE access** | Your local IDE | Any IDE connected to your cloud environment |
-| **REST API** | Yes | Yes |
-| **n8n node** | No | Yes |
-| **Team sharing** | No | Yes |
-| **Platform cost** | Free, open source | Starts at $7/month |
+| | Self-Hosted (npm) | Self-Hosted (Docker Sandbox) *(Experimental)* | CloudCLI Cloud |
+|---|---|---|---|
+| **Best for** | Local agent sessions on your own machine | Isolated agents with web/mobile IDE | Teams who want agents in the cloud |
+| **How you access it** | Browser via `[yourip]:port` | Browser via `localhost:port` | Browser, any IDE, REST API, n8n |
+| **Setup** | `npx @cloudcli-ai/cloudcli` | `npx @cloudcli-ai/cloudcli sandbox ~/project` | No setup required |
+| **Isolation** | Runs on your host | Hypervisor-level sandbox (microVM) | Full cloud isolation |
+| **Machine needs to stay on** | Yes | Yes | No |
+| **Mobile access** | Any browser on your network | Any browser on your network | Any device, native app coming |
+| **Agents supported** | Claude Code, Cursor CLI, Codex, Gemini CLI | Claude Code, Codex, Gemini CLI | Claude Code, Cursor CLI, Codex, Gemini CLI |
+| **File explorer and Git** | Yes | Yes | Yes |
+| **MCP configuration** | Synced with `~/.claude` | Managed via UI | Managed via UI |
+| **REST API** | Yes | Yes | Yes |
+| **Team sharing** | No | No | Yes |
+| **Platform cost** | Free, open source | Free, open source | Starts at $7/month |
-> Both options use your own AI subscriptions (Claude, Cursor, etc.) — CloudCLI provides the environment, not the AI.
+> All options use your own AI subscriptions (Claude, Cursor, etc.) — CloudCLI provides the environment, not the AI.
---
diff --git a/README.ru.md b/README.ru.md
index c7561ba6..55b936a8 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -76,6 +76,8 @@
### Self-Hosted (Open source)
+#### npm
+
Попробовать CloudCLI UI можно сразу через **npx** (требуется **Node.js** v22+):
```bash
@@ -91,8 +93,17 @@ cloudcli
Откройте `http://localhost:3001` — все ваши существующие сессии будут обнаружены автоматически.
-Посетите **[документацию →](https://cloudcli.ai/docs)**, чтобы узнать про дополнительные варианты конфигурации, PM2, настройку удалённого сервера и многое другое
+Посетите **[документацию →](https://cloudcli.ai/docs)**, чтобы узнать про дополнительные варианты конфигурации, PM2, настройку удалённого сервера и многое другое.
+#### Docker Sandboxes (Экспериментально)
+
+Запускайте агентов в изолированных песочницах с гипервизорной изоляцией. По умолчанию запускается Claude Code. Требуется [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/).
+
+```
+npx @cloudcli-ai/cloudcli sandbox ~/my-project
+```
+
+Поддерживаются Claude Code, Codex и Gemini CLI. Подробнее в [документации sandbox](docker/).
---
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 690af7d8..3f18d3cd 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -72,6 +72,8 @@
### 自托管(开源)
+#### npm
+
启动 CloudCLI UI,只需一行 `npx`(需要 Node.js v22+):
```bash
@@ -87,7 +89,17 @@ cloudcli
打开 `http://localhost:3001`,系统会自动发现所有现有会话。
-更多配置选项、PM2、远程服务器设置等,请参阅 **[文档 →](https://cloudcli.ai/docs)**
+更多配置选项、PM2、远程服务器设置等,请参阅 **[文档 →](https://cloudcli.ai/docs)**。
+
+#### Docker Sandboxes(实验性)
+
+在隔离的沙箱中运行代理,具有虚拟机管理程序级别的隔离。默认启动 Claude Code。需要 [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/)。
+
+```
+npx @cloudcli-ai/cloudcli sandbox ~/my-project
+```
+
+支持 Claude Code、Codex 和 Gemini CLI。详情请参阅 [沙箱文档](docker/)。
---
diff --git a/docker/README.md b/docker/README.md
index 780fcf93..c093eccb 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,89 +1,146 @@
-# CloudCLI — Docker Sandbox Templates
+
+
-Run AI coding agents with a full web IDE inside [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/).
+# Sandboxed coding agents with a web & mobile IDE (CloudCLI)
-Instead of a terminal-only experience, get a browser-based interface with chat, file explorer, git panel, shell, and MCP configuration — all running safely inside an isolated sandbox.
+[Docker Sandbox](https://docs.docker.com/ai/sandboxes/) templates that add [CloudCLI](https://cloudcli.ai) on top of Claude Code, Codex, and Gemini CLI. You get a full web and mobile IDE accessible from any browser on any device.
-## Available Templates
+## Get started
-| Template | Base Image | Agent |
-|----------|-----------|-------|
-| `cloudcli-ai/sandbox:claude-code` | `docker/sandbox-templates:claude-code` | Claude Code |
-| `cloudcli-ai/sandbox:codex` | `docker/sandbox-templates:codex` | OpenAI Codex |
-| `cloudcli-ai/sandbox:gemini` | `docker/sandbox-templates:gemini` | Gemini CLI |
+### 1. Install the sbx CLI
-## Quick Start
+Docker Sandboxes run agents in isolated microVMs. Install the `sbx` CLI:
-### 1. Start a sandbox with the template
+- **macOS**: `brew install docker/tap/sbx`
+- **Windows**: `winget install -h Docker.sbx`
+- **Linux**: `sudo apt-get install docker-sbx`
+
+Full instructions: [docs.docker.com/ai/sandboxes/get-started](https://docs.docker.com/ai/sandboxes/get-started/)
+
+### 2. Store your API key
+
+`sbx` manages credentials securely — your API key never enters the sandbox. Store it once:
```bash
-sbx run --template docker.io/cloudcli-ai/sandbox:claude-code claude ~/my-project
+sbx login
+sbx secret set -g anthropic
```
-### 2. Forward the UI port
+### 3. Launch Claude Code
```bash
-sbx ports --publish 3001:3001
+npx @cloudcli-ai/cloudcli sandbox ~/my-project
```
-### 3. Open the browser
+Open **http://localhost:3001**. Set a password on first visit. Start building.
-```
-http://localhost:3001
+### Using a different agent
+
+Store the matching API key and pass `--agent`:
+
+```bash
+# OpenAI Codex
+sbx secret set -g openai
+npx @cloudcli-ai/cloudcli sandbox ~/my-project --agent codex
+
+# Gemini CLI
+sbx secret set -g google
+npx @cloudcli-ai/cloudcli sandbox ~/my-project --agent gemini
```
-On first visit you'll set a password — this protects the UI if the port is ever exposed beyond localhost.
+### Available templates
-## What You Get
+| Agent | Template |
+|-------|----------|
+| **Claude Code** (default) | `docker.io/cloudcliai/sandbox:claude-code` |
+| OpenAI Codex | `docker.io/cloudcliai/sandbox:codex` |
+| Gemini CLI | `docker.io/cloudcliai/sandbox:gemini` |
-- **Chat** — Rich conversation UI with markdown rendering, code blocks, and message history
-- **Files** — Visual file tree with syntax-highlighted editor
-- **Git** — Diff viewer, staging, branch switching, and commit — all visual
+These are used with `--template` when running `sbx` directly (see [Advanced usage](#advanced-usage)).
+
+## Managing sandboxes
+
+```bash
+cloudcli sandbox ls # List all sandboxes
+cloudcli sandbox stop my-project # Stop (preserves state)
+cloudcli sandbox start my-project # Restart and re-launch web UI
+cloudcli sandbox logs my-project # View server logs
+cloudcli sandbox rm my-project # Remove everything
+```
+
+## What you get
+
+- **Chat** — Markdown rendering, code blocks, message history
+- **Files** — File tree with syntax-highlighted editor
+- **Git** — Diff viewer, staging, branch switching, commits
- **Shell** — Built-in terminal emulator
-- **MCP** — Configure Model Context Protocol servers through the UI
+- **MCP** — Configure Model Context Protocol servers visually
- **Mobile** — Works on tablet and phone browsers
-## Building Locally
-
-All Dockerfiles share scripts from `shared/`. Build with the `docker/` directory as context:
-
-```bash
-# Claude Code variant
-docker build -f docker/claude-code/Dockerfile -t cloudcli-sandbox:claude-code docker/
-
-# Codex variant
-docker build -f docker/codex/Dockerfile -t cloudcli-sandbox:codex docker/
-
-# Gemini variant
-docker build -f docker/gemini/Dockerfile -t cloudcli-sandbox:gemini docker/
-```
-
-## How It Works
-
-Each template extends Docker's official sandbox base image and adds:
-
-1. **Node.js 22** — Runtime for CloudCLI
-2. **CloudCLI** — Installed globally via `npm install -g @cloudcli-ai/cloudcli`
-3. **Auto-start** — The UI server starts in the background when the sandbox shell opens (port 3001)
-
-The agent (Claude Code, Codex, or Gemini) comes from the base image. CloudCLI connects to it and provides the web interface on top.
+Your project directory is mounted bidirectionally — edits propagate in real time, both ways.
## Configuration
-| Environment Variable | Default | Description |
-|---------------------|---------|-------------|
-| `SERVER_PORT` | `3001` | Port for the web UI |
-| `HOST` | `0.0.0.0` | Bind address |
-| `DATABASE_PATH` | `~/.cloudcli/auth.db` | SQLite database location |
-
-## Network Policies
-
-If your sandbox uses restricted network policies, allow the UI port:
+Set variables at creation time with `--env`:
```bash
-sbx policy allow network "localhost:3001"
+npx @cloudcli-ai/cloudcli sandbox ~/my-project --env SERVER_PORT=8080
```
+Or inside a running sandbox:
+
+```bash
+sbx exec my-project bash -c 'echo "export SERVER_PORT=8080" >> /etc/sandbox-persistent.sh'
+sbx exec my-project bash -c 'pkill -f "server/index.js"; . ~/.cloudcli-start.sh'
+```
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `SERVER_PORT` | `3001` | Web UI port |
+| `HOST` | `0.0.0.0` | Bind address (must be `0.0.0.0` for `sbx ports`) |
+| `DATABASE_PATH` | `~/.cloudcli/auth.db` | SQLite database location |
+
+## Advanced usage
+
+For branch mode, multiple workspaces, memory limits, or the terminal agent experience, use `sbx` with the template:
+
+```bash
+# Terminal agent + web UI
+sbx run --template docker.io/cloudcliai/sandbox:claude-code claude ~/my-project --name my-project
+sbx ports my-project --publish 3001:3001
+
+# Branch mode (Git worktree isolation)
+sbx run --template docker.io/cloudcliai/sandbox:claude-code claude ~/my-project --branch my-feature
+
+# Multiple workspaces
+sbx run --template docker.io/cloudcliai/sandbox:claude-code claude ~/project ~/shared-libs:ro
+
+# Pass a prompt directly
+sbx run --template docker.io/cloudcliai/sandbox:claude-code claude ~/my-project -- "Fix the auth bug"
+```
+
+CloudCLI auto-starts via `.bashrc` when using `sbx run`.
+
+Full options in the [Docker Sandboxes usage guide](https://docs.docker.com/ai/sandboxes/usage/).
+
+## Network policies
+
+Sandboxes restrict outbound access by default. To reach host services from inside the sandbox:
+
+```bash
+sbx policy allow network localhost:11434
+# Inside the sandbox: curl http://host.docker.internal:11434
+```
+
+The web UI itself doesn't need a policy — access it via `sbx ports`.
+
+## Links
+
+- [CloudCLI Cloud](https://cloudcli.ai) — fully managed, no setup required
+- [Documentation](https://cloudcli.ai/docs) — full configuration guide
+- [Discord](https://discord.gg/buxwujPNRE) — community support
+- [GitHub](https://github.com/siteboon/claudecodeui) — source code and issues
+
## License
-These templates are free and open-source under the same license as CloudCLI (AGPL-3.0-or-later).
+AGPL-3.0-or-later
diff --git a/docker/claude-code/Dockerfile b/docker/claude-code/Dockerfile
index ed04cf6b..8d108a80 100644
--- a/docker/claude-code/Dockerfile
+++ b/docker/claude-code/Dockerfile
@@ -5,7 +5,7 @@ COPY shared/install-cloudcli.sh /tmp/install-cloudcli.sh
RUN chmod +x /tmp/install-cloudcli.sh && /tmp/install-cloudcli.sh
USER agent
-RUN npm install -g @cloudcli-ai/cloudcli
+RUN npm install -g @cloudcli-ai/cloudcli && cloudcli --version
-COPY shared/start-cloudcli.sh /home/agent/.cloudcli-start.sh
+COPY --chown=agent:agent shared/start-cloudcli.sh /home/agent/.cloudcli-start.sh
RUN echo '. ~/.cloudcli-start.sh' >> /home/agent/.bashrc
diff --git a/docker/codex/Dockerfile b/docker/codex/Dockerfile
index 9948d0af..c1bc1807 100644
--- a/docker/codex/Dockerfile
+++ b/docker/codex/Dockerfile
@@ -5,7 +5,7 @@ COPY shared/install-cloudcli.sh /tmp/install-cloudcli.sh
RUN chmod +x /tmp/install-cloudcli.sh && /tmp/install-cloudcli.sh
USER agent
-RUN npm install -g @cloudcli-ai/cloudcli
+RUN npm install -g @cloudcli-ai/cloudcli && cloudcli --version
-COPY shared/start-cloudcli.sh /home/agent/.cloudcli-start.sh
+COPY --chown=agent:agent shared/start-cloudcli.sh /home/agent/.cloudcli-start.sh
RUN echo '. ~/.cloudcli-start.sh' >> /home/agent/.bashrc
diff --git a/docker/gemini/Dockerfile b/docker/gemini/Dockerfile
index ec7209f0..dc06db69 100644
--- a/docker/gemini/Dockerfile
+++ b/docker/gemini/Dockerfile
@@ -5,7 +5,7 @@ COPY shared/install-cloudcli.sh /tmp/install-cloudcli.sh
RUN chmod +x /tmp/install-cloudcli.sh && /tmp/install-cloudcli.sh
USER agent
-RUN npm install -g @cloudcli-ai/cloudcli
+RUN npm install -g @cloudcli-ai/cloudcli && cloudcli --version
-COPY shared/start-cloudcli.sh /home/agent/.cloudcli-start.sh
+COPY --chown=agent:agent shared/start-cloudcli.sh /home/agent/.cloudcli-start.sh
RUN echo '. ~/.cloudcli-start.sh' >> /home/agent/.bashrc
diff --git a/docker/shared/install-cloudcli.sh b/docker/shared/install-cloudcli.sh
index 0390383e..4b43fe22 100644
--- a/docker/shared/install-cloudcli.sh
+++ b/docker/shared/install-cloudcli.sh
@@ -1,13 +1,10 @@
#!/bin/bash
set -e
-# Install Node.js 22 LTS
-curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
-
-# Install Node.js + build tools needed for native modules (node-pty, better-sqlite3, bcrypt)
-# Node.js + build tools for native modules + common dev tools
-apt-get install -y --no-install-recommends \
- nodejs build-essential python3 python3-setuptools \
+# Install build tools needed for native modules (node-pty, better-sqlite3, bcrypt)
+# Node.js is already provided by the sandbox base image
+apt-get update && apt-get install -y --no-install-recommends \
+ build-essential python3 python3-setuptools \
jq ripgrep sqlite3 zip unzip tree vim-tiny
# Clean up apt cache to reduce image size
diff --git a/docker/shared/start-cloudcli.sh b/docker/shared/start-cloudcli.sh
index 145c35c5..82b2fdc2 100644
--- a/docker/shared/start-cloudcli.sh
+++ b/docker/shared/start-cloudcli.sh
@@ -4,19 +4,14 @@
# This script is sourced from ~/.bashrc on sandbox shell open.
if ! pgrep -f "server/index.js" > /dev/null 2>&1; then
- # Start the pre-installed version immediately
nohup cloudcli start --port 3001 > /tmp/cloudcli-ui.log 2>&1 &
disown
- # Check for updates in the background (non-blocking)
- nohup npm update -g @cloudcli-ai/cloudcli > /tmp/cloudcli-update.log 2>&1 &
- disown
-
echo ""
echo " CloudCLI is starting on port 3001..."
echo ""
- echo " To access the web UI, forward the port:"
- echo " sbx ports \$(hostname) --publish 3001:3001"
+ echo " Forward the port from another terminal:"
+ echo " sbx ports --publish 3001:3001"
echo ""
echo " Then open: http://localhost:3001"
echo ""
diff --git a/server/cli.js b/server/cli.js
index 39461673..dad74268 100755
--- a/server/cli.js
+++ b/server/cli.js
@@ -7,6 +7,7 @@
* Commands:
* (no args) - Start the server (default)
* start - Start the server
+ * sandbox - Manage Docker sandbox environments
* status - Show configuration and data locations
* help - Show help information
* version - Show version information
@@ -150,6 +151,7 @@ Usage:
Commands:
start Start the CloudCLI server (default)
+ sandbox Manage Docker sandbox environments
status Show configuration and data locations
update Update to the latest version
help Show this help information
@@ -164,8 +166,7 @@ Options:
Examples:
$ cloudcli # Start with defaults
$ cloudcli --port 8080 # Start on port 8080
- $ cloudcli -p 3000 # Short form for port
- $ cloudcli start --port 4000 # Explicit start command
+ $ cloudcli sandbox ~/my-project # Run in a Docker sandbox
$ cloudcli status # Show configuration
Environment Variables:
@@ -244,6 +245,357 @@ async function updatePackage() {
}
}
+// ── Sandbox command ─────────────────────────────────────────
+
+const SANDBOX_TEMPLATES = {
+ claude: 'docker.io/cloudcliai/sandbox:claude-code',
+ codex: 'docker.io/cloudcliai/sandbox:codex',
+ gemini: 'docker.io/cloudcliai/sandbox:gemini',
+};
+
+const SANDBOX_SECRETS = {
+ claude: 'anthropic',
+ codex: 'openai',
+ gemini: 'google',
+};
+
+function parseSandboxArgs(args) {
+ const result = {
+ subcommand: null,
+ workspace: null,
+ agent: 'claude',
+ name: null,
+ port: 3001,
+ template: null,
+ env: [],
+ };
+
+ const subcommands = ['ls', 'stop', 'start', 'rm', 'logs', 'help'];
+
+ for (let i = 0; i < args.length; i++) {
+ const arg = args[i];
+
+ if (i === 0 && subcommands.includes(arg)) {
+ result.subcommand = arg;
+ } else if (arg === '--agent' || arg === '-a') {
+ result.agent = args[++i];
+ } else if (arg === '--name' || arg === '-n') {
+ result.name = args[++i];
+ } else if (arg === '--port') {
+ result.port = parseInt(args[++i], 10);
+ } else if (arg === '--template' || arg === '-t') {
+ result.template = args[++i];
+ } else if (arg === '--env' || arg === '-e') {
+ result.env.push(args[++i]);
+ } else if (!arg.startsWith('-')) {
+ if (!result.subcommand) {
+ result.workspace = arg;
+ } else {
+ result.name = arg; // for stop/start/rm/logs
+ }
+ }
+ }
+
+ // Default subcommand based on what we got
+ if (!result.subcommand) {
+ result.subcommand = 'create';
+ }
+
+ // Derive name from workspace path if not set
+ if (!result.name && result.workspace) {
+ result.name = path.basename(path.resolve(result.workspace.replace(/^~/, os.homedir())));
+ }
+
+ // Default template from agent
+ if (!result.template) {
+ result.template = SANDBOX_TEMPLATES[result.agent] || SANDBOX_TEMPLATES.claude;
+ }
+
+ return result;
+}
+
+function showSandboxHelp() {
+ console.log(`
+${c.bright('CloudCLI Sandbox')} — Run CloudCLI inside Docker Sandboxes
+
+Usage:
+ cloudcli sandbox Create and start a sandbox
+ cloudcli sandbox [name] Manage sandboxes
+
+Subcommands:
+ ${c.bright('(default)')} Create a sandbox and start the web UI
+ ${c.bright('ls')} List all sandboxes
+ ${c.bright('start')} Restart a stopped sandbox and re-launch the web UI
+ ${c.bright('stop')} Stop a sandbox (preserves state)
+ ${c.bright('rm')} Remove a sandbox
+ ${c.bright('logs')} Show CloudCLI server logs
+ ${c.bright('help')} Show this help
+
+Options:
+ -a, --agent Agent to use: claude, codex, gemini (default: claude)
+ -n, --name Sandbox name (default: derived from workspace folder)
+ -t, --template Custom template image
+ -e, --env Set environment variable (repeatable)
+ --port Host port for the web UI (default: 3001)
+
+Examples:
+ $ cloudcli sandbox ~/my-project
+ $ cloudcli sandbox ~/my-project --agent codex --port 8080
+ $ cloudcli sandbox ~/my-project --env SERVER_PORT=8080 --env HOST=0.0.0.0
+ $ cloudcli sandbox ls
+ $ cloudcli sandbox stop my-project
+ $ cloudcli sandbox start my-project
+ $ cloudcli sandbox rm my-project
+
+Prerequisites:
+ 1. Install sbx CLI: https://docs.docker.com/ai/sandboxes/get-started/
+ 2. Authenticate and store your API key:
+ sbx login
+ sbx secret set -g anthropic # for Claude
+ sbx secret set -g openai # for Codex
+ sbx secret set -g google # for Gemini
+
+Advanced usage:
+ For branch mode, multiple workspaces, memory limits, network policies,
+ or passing prompts to the agent, use sbx directly with the template:
+
+ sbx run --template docker.io/cloudcliai/sandbox:claude-code claude ~/my-project --branch my-feature
+ sbx run --template docker.io/cloudcliai/sandbox:claude-code claude ~/project ~/libs:ro --memory 8g
+
+ Full Docker Sandboxes docs: https://docs.docker.com/ai/sandboxes/usage/
+`);
+}
+
+async function sandboxCommand(args) {
+ const { execFileSync } = await import('child_process');
+
+ // Safe execution — uses execFileSync (no shell) to prevent injection
+ const sbx = (subcmd, opts = {}) => {
+ const result = execFileSync('sbx', subcmd, {
+ encoding: 'utf8',
+ stdio: opts.inherit ? 'inherit' : 'pipe',
+ });
+ return result || '';
+ };
+
+ const opts = parseSandboxArgs(args);
+
+ if (opts.subcommand === 'help') {
+ showSandboxHelp();
+ return;
+ }
+
+ // Validate name (alphanumeric, hyphens, underscores only)
+ if (opts.name && !/^[\w-]+$/.test(opts.name)) {
+ console.error(`\n${c.error('❌')} Invalid sandbox name: ${opts.name}`);
+ console.log(` Names may only contain letters, numbers, hyphens, and underscores.\n`);
+ process.exit(1);
+ }
+
+ // Check sbx is installed
+ try {
+ sbx(['version']);
+ } catch {
+ console.error(`\n${c.error('❌')} ${c.bright('sbx')} CLI not found.\n`);
+ console.log(` Install it from: ${c.info('https://docs.docker.com/ai/sandboxes/get-started/')}`);
+ console.log(` Then run: ${c.bright('sbx login')}`);
+ console.log(` And store your API key: ${c.bright('sbx secret set -g anthropic')}\n`);
+ process.exit(1);
+ }
+
+ switch (opts.subcommand) {
+
+ case 'ls':
+ sbx(['ls'], { inherit: true });
+ break;
+
+ case 'stop':
+ if (!opts.name) {
+ console.error(`\n${c.error('❌')} Sandbox name required: cloudcli sandbox stop \n`);
+ process.exit(1);
+ }
+ sbx(['stop', opts.name], { inherit: true });
+ break;
+
+ case 'rm':
+ if (!opts.name) {
+ console.error(`\n${c.error('❌')} Sandbox name required: cloudcli sandbox rm \n`);
+ process.exit(1);
+ }
+ sbx(['rm', opts.name], { inherit: true });
+ break;
+
+ case 'logs':
+ if (!opts.name) {
+ console.error(`\n${c.error('❌')} Sandbox name required: cloudcli sandbox logs \n`);
+ process.exit(1);
+ }
+ try {
+ sbx(['exec', opts.name, 'bash', '-c', 'cat /tmp/cloudcli-ui.log'], { inherit: true });
+ } catch (e) {
+ console.error(`\n${c.error('❌')} Could not read logs: ${e.message || 'Is the sandbox running?'}\n`);
+ }
+ break;
+
+ case 'start': {
+ if (!opts.name) {
+ console.error(`\n${c.error('❌')} Sandbox name required: cloudcli sandbox start \n`);
+ process.exit(1);
+ }
+ console.log(`\n${c.info('▶')} Starting sandbox ${c.bright(opts.name)}...`);
+ try {
+ sbx(['start', opts.name], { inherit: true });
+ } catch { /* might already be running */ }
+
+ console.log(`${c.info('▶')} Launching CloudCLI web server...`);
+ sbx(['exec', '-d', opts.name, 'bash', '-c', '. ~/.cloudcli-start.sh']);
+
+ console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
+ try {
+ sbx(['ports', opts.name, '--publish', `${opts.port}:3001`]);
+ } catch (e) {
+ const msg = e.stdout || e.stderr || e.message || '';
+ if (msg.includes('address already in use')) {
+ const altPort = opts.port + 1;
+ console.log(`${c.warn('⚠')} Port ${opts.port} in use, trying ${altPort}...`);
+ try {
+ sbx(['ports', opts.name, '--publish', `${altPort}:3001`]);
+ opts.port = altPort;
+ } catch {
+ console.error(`${c.error('❌')} Ports ${opts.port} and ${altPort} both in use. Use --port to specify a free port.`);
+ process.exit(1);
+ }
+ } else {
+ throw e;
+ }
+ }
+
+ console.log(`\n${c.ok('✔')} ${c.bright('CloudCLI is ready!')}`);
+ console.log(` ${c.info('→')} ${c.bright(`http://localhost:${opts.port}`)}\n`);
+ break;
+ }
+
+ case 'create': {
+ if (!opts.workspace) {
+ console.error(`\n${c.error('❌')} Workspace path required: cloudcli sandbox \n`);
+ console.log(` Example: ${c.bright('cloudcli sandbox ~/my-project')}\n`);
+ process.exit(1);
+ }
+
+ const workspace = opts.workspace.startsWith('~')
+ ? opts.workspace.replace(/^~/, os.homedir())
+ : path.resolve(opts.workspace);
+
+ if (!fs.existsSync(workspace)) {
+ console.error(`\n${c.error('❌')} Workspace path not found: ${c.dim(workspace)}\n`);
+ process.exit(1);
+ }
+
+ const secret = SANDBOX_SECRETS[opts.agent] || 'anthropic';
+
+ // Check if the required secret is stored
+ try {
+ const secretList = sbx(['secret', 'ls']);
+ if (!secretList.includes(secret)) {
+ console.error(`\n${c.error('❌')} No ${c.bright(secret)} API key found.\n`);
+ console.log(` Run: ${c.bright(`sbx secret set -g ${secret}`)}\n`);
+ process.exit(1);
+ }
+ } catch { /* sbx secret ls not available, skip check */ }
+
+ console.log(`\n${c.bright('CloudCLI Sandbox')}`);
+ console.log(c.dim('─'.repeat(50)));
+ console.log(` Agent: ${c.info(opts.agent)} ${c.dim(`(${secret} credentials)`)}`);
+ console.log(` Workspace: ${c.dim(workspace)}`);
+ console.log(` Name: ${c.dim(opts.name)}`);
+ console.log(` Template: ${c.dim(opts.template)}`);
+ console.log(` Port: ${c.dim(String(opts.port))}`);
+ if (opts.env.length > 0) {
+ console.log(` Env: ${c.dim(opts.env.join(', '))}`);
+ }
+ console.log(c.dim('─'.repeat(50)));
+
+ // Step 1: Create sandbox
+ console.log(`\n${c.info('▶')} Creating sandbox ${c.bright(opts.name)}...`);
+ try {
+ sbx(
+ ['create', '--template', opts.template, '--name', opts.name, opts.agent, workspace],
+ { inherit: true }
+ );
+ } catch (e) {
+ const msg = e.stdout || e.stderr || e.message || '';
+ if (msg.includes('already exists')) {
+ console.log(`${c.warn('⚠')} Sandbox ${c.bright(opts.name)} already exists. Starting it instead...\n`);
+ try { sbx(['start', opts.name]); } catch { /* may already be running */ }
+ } else {
+ throw e;
+ }
+ }
+
+ // Step 2: Inject environment variables
+ if (opts.env.length > 0) {
+ console.log(`${c.info('▶')} Setting environment variables...`);
+ const exports = opts.env
+ .filter(e => /^\w+=.+$/.test(e))
+ .map(e => `export ${e}`)
+ .join('\n');
+ if (exports) {
+ sbx(['exec', opts.name, 'bash', '-c', `echo '${exports}' >> /etc/sandbox-persistent.sh`]);
+ }
+ const invalid = opts.env.filter(e => !/^\w+=.+$/.test(e));
+ if (invalid.length > 0) {
+ console.log(`${c.warn('⚠')} Skipped invalid env vars: ${invalid.join(', ')} (expected KEY=VALUE)`);
+ }
+ }
+
+ // Step 3: Start CloudCLI
+ console.log(`${c.info('▶')} Launching CloudCLI web server...`);
+ try {
+ sbx(['exec', '-d', opts.name, 'bash', '-c', '. ~/.cloudcli-start.sh']);
+ } catch (e) {
+ console.error(`${c.error('❌')} Failed to start CloudCLI: ${e.message}`);
+ process.exit(1);
+ }
+
+ // Step 4: Forward port
+ console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
+ try {
+ sbx(['ports', opts.name, '--publish', `${opts.port}:3001`]);
+ } catch (e) {
+ const msg = e.stdout || e.stderr || e.message || '';
+ if (msg.includes('address already in use')) {
+ const altPort = opts.port + 1;
+ console.log(`${c.warn('⚠')} Port ${opts.port} in use, trying ${altPort}...`);
+ try {
+ sbx(['ports', opts.name, '--publish', `${altPort}:3001`]);
+ opts.port = altPort;
+ } catch {
+ console.error(`${c.error('❌')} Ports ${opts.port} and ${altPort} both in use. Use --port to specify a free port.`);
+ process.exit(1);
+ }
+ } else {
+ throw e;
+ }
+ }
+
+ // Done
+ console.log(`\n${c.ok('✔')} ${c.bright('CloudCLI is ready!')}`);
+ console.log(` ${c.info('→')} Open ${c.bright(`http://localhost:${opts.port}`)}`);
+ console.log(`\n${c.dim(' Manage with:')}`);
+ console.log(` ${c.dim('$')} cloudcli sandbox ls`);
+ console.log(` ${c.dim('$')} cloudcli sandbox stop ${opts.name}`);
+ console.log(` ${c.dim('$')} cloudcli sandbox start ${opts.name}`);
+ console.log(` ${c.dim('$')} cloudcli sandbox rm ${opts.name}\n`);
+ break;
+ }
+
+ default:
+ showSandboxHelp();
+ }
+}
+
+// ── Server ──────────────────────────────────────────────────
+
// Start the server
async function startServer() {
// Check for updates silently on startup
@@ -274,6 +626,10 @@ function parseArgs(args) {
parsed.command = 'version';
} else if (!arg.startsWith('-')) {
parsed.command = arg;
+ if (arg === 'sandbox') {
+ parsed.remainingArgs = args.slice(i + 1);
+ break;
+ }
}
}
@@ -283,7 +639,7 @@ function parseArgs(args) {
// Main CLI handler
async function main() {
const args = process.argv.slice(2);
- const { command, options } = parseArgs(args);
+ const { command, options, remainingArgs } = parseArgs(args);
// Apply CLI options to environment variables
if (options.serverPort) {
@@ -299,6 +655,9 @@ async function main() {
case 'start':
await startServer();
break;
+ case 'sandbox':
+ await sandboxCommand(remainingArgs || []);
+ break;
case 'status':
case 'info':
showStatus();
From d0dd007d0fb9a6bcb040a6b9f0e62ee496a8db4c Mon Sep 17 00:00:00 2001
From: Haile <118998054+blackmammoth@users.noreply.github.com>
Date: Tue, 14 Apr 2026 18:18:47 +0300
Subject: [PATCH 07/14] Feature/restart server on update (#652)
* feat: support restart server on update for platform
* feat: add update platform script to package.json
* feat: optimize platform update command by omitting dev dependencies
* feat: simplify update commands for platform
---------
Co-authored-by: Haileyesus
Co-authored-by: Simos Mikelatos
---
package.json | 3 ++-
server/index.js | 17 ++++++++++++-----
.../view/VersionUpgradeModal.tsx | 8 ++++++--
3 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/package.json b/package.json
index c3fc7358..f2af9ce7 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,8 @@
"release": "./release.sh",
"prepublishOnly": "npm run build",
"postinstall": "node scripts/fix-node-pty.js",
- "prepare": "husky"
+ "prepare": "husky",
+ "update:platform": "./update-platform.sh"
},
"keywords": [
"claude code",
diff --git a/server/index.js b/server/index.js
index d295fae4..da6ca675 100755
--- a/server/index.js
+++ b/server/index.js
@@ -435,13 +435,20 @@ app.post('/api/system/update', authenticateToken, async (req, res) => {
console.log('Starting system update from directory:', projectRoot);
- // Run the update command based on install mode
- const updateCommand = installMode === 'git'
- ? 'git checkout main && git pull && npm install'
- : 'npm install -g @cloudcli-ai/cloudcli@latest';
+ // Platform deployments use their own update workflow from the project root.
+ const updateCommand = IS_PLATFORM
+ // In platform, husky and dev dependencies are not needed
+ ? 'npm run update:platform'
+ : installMode === 'git'
+ ? 'git checkout main && git pull && npm install'
+ : 'npm install -g @cloudcli-ai/cloudcli@latest';
+
+ const updateCwd = IS_PLATFORM || installMode === 'git'
+ ? projectRoot
+ : os.homedir();
const child = spawn('sh', ['-c', updateCommand], {
- cwd: installMode === 'git' ? projectRoot : os.homedir(),
+ cwd: updateCwd,
env: process.env
});
diff --git a/src/components/version-upgrade/view/VersionUpgradeModal.tsx b/src/components/version-upgrade/view/VersionUpgradeModal.tsx
index d4570ee5..c9be12d2 100644
--- a/src/components/version-upgrade/view/VersionUpgradeModal.tsx
+++ b/src/components/version-upgrade/view/VersionUpgradeModal.tsx
@@ -4,6 +4,7 @@ import { authenticatedFetch } from "../../../utils/api";
import { ReleaseInfo } from "../../../types/sharedTypes";
import { copyTextToClipboard } from "../../../utils/clipboard";
import type { InstallMode } from "../../../hooks/useVersionCheck";
+import { IS_PLATFORM } from "../../../constants/config";
interface VersionUpgradeModalProps {
isOpen: boolean;
@@ -25,7 +26,9 @@ export function VersionUpgradeModal({
const { t } = useTranslation('common');
const upgradeCommand = installMode === 'npm'
? t('versionUpdate.npmUpgradeCommand')
- : 'git checkout main && git pull && npm install';
+ : IS_PLATFORM
+ ? 'npm run update:platform'
+ : 'git checkout main && git pull && npm install';
const [isUpdating, setIsUpdating] = useState(false);
const [updateOutput, setUpdateOutput] = useState('');
const [updateError, setUpdateError] = useState('');
@@ -46,7 +49,8 @@ export function VersionUpgradeModal({
if (response.ok) {
setUpdateOutput(prev => prev + data.output + '\n');
setUpdateOutput(prev => prev + '\n✅ Update completed successfully!\n');
- setUpdateOutput(prev => prev + 'Please restart the server to apply changes.\n');
+ const text = IS_PLATFORM ? 'Please refresh the page after 5 seconds to load the new version. If that doesn\'t work, RESTART the environment.' : 'Please restart the server to apply changes.';
+ setUpdateOutput(prev => prev + text + '\n');
} else {
setUpdateError(data.error || 'Update failed');
setUpdateOutput(prev => prev + '\n❌ Update failed: ' + (data.error || 'Unknown error') + '\n');
From 6ce3306947d40014e4df2463de04916a4f107404 Mon Sep 17 00:00:00 2001
From: viper151
Date: Tue, 14 Apr 2026 15:20:18 +0000
Subject: [PATCH 08/14] chore(release): v1.29.0
---
CHANGELOG.md | 14 ++++++++++++++
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1f49d9f..daac93a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,20 @@
All notable changes to CloudCLI UI will be documented in this file.
+## [1.29.0](https://github.com/siteboon/claudecodeui/compare/v1.28.1...v1.29.0) (2026-04-14)
+
+### New Features
+
+* adding docker sandbox environments ([13e97e2](https://github.com/siteboon/claudecodeui/commit/13e97e2c71254de7a60afb5495b21064c4bc4241))
+
+### Bug Fixes
+
+* **thinking-mode:** fix dropdown positioning ([#646](https://github.com/siteboon/claudecodeui/issues/646)) ([c7a5baf](https://github.com/siteboon/claudecodeui/commit/c7a5baf1479404bd40e23aa58bd9f677df9a04c6))
+
+### Maintenance
+
+* update release flow node version ([e2459cb](https://github.com/siteboon/claudecodeui/commit/e2459cb0f8b35f54827778a7b444e6c3ca326506))
+
## [1.28.1](https://github.com/siteboon/claudecodeui/compare/v1.28.0...v1.28.1) (2026-04-10)
### New Features
diff --git a/package-lock.json b/package-lock.json
index 1e80b8bd..192b06eb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@cloudcli-ai/cloudcli",
- "version": "1.28.1",
+ "version": "1.29.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@cloudcli-ai/cloudcli",
- "version": "1.28.1",
+ "version": "1.29.0",
"hasInstallScript": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
diff --git a/package.json b/package.json
index f2af9ce7..1b94330e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@cloudcli-ai/cloudcli",
- "version": "1.28.1",
+ "version": "1.29.0",
"description": "A web-based UI for Claude Code CLI",
"type": "module",
"main": "server/index.js",
From 4a569725dae320a505753359d8edfd8ca79f0fd7 Mon Sep 17 00:00:00 2001
From: simosmik
Date: Tue, 14 Apr 2026 17:37:20 +0000
Subject: [PATCH 09/14] fix: add latest tag to docker npx command and change
the detach mode to work without spawn
---
README.de.md | 2 +-
README.ja.md | 2 +-
README.ko.md | 2 +-
README.md | 4 ++--
README.ru.md | 2 +-
README.zh-CN.md | 2 +-
docker/README.md | 30 ++++++++++++++++++++++--------
server/cli.js | 22 ++++++++++------------
8 files changed, 39 insertions(+), 27 deletions(-)
diff --git a/README.de.md b/README.de.md
index 4e354020..461f59a8 100644
--- a/README.de.md
+++ b/README.de.md
@@ -100,7 +100,7 @@ Die **[Dokumentation →](https://cloudcli.ai/docs)** enthält weitere Konfigura
Agents in isolierten Sandboxes mit Hypervisor-Isolation ausführen. Standardmäßig wird Claude Code gestartet. Erfordert die [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/).
```
-npx @cloudcli-ai/cloudcli sandbox ~/my-project
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
```
Unterstützt Claude Code, Codex und Gemini CLI. Weitere Details in der [Sandbox-Dokumentation](docker/).
diff --git a/README.ja.md b/README.ja.md
index dfca8cda..a3c7d04b 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -96,7 +96,7 @@ cloudcli
ハイパーバイザーレベルの分離でエージェントをサンドボックスで実行します。デフォルトでは Claude Code が起動します。[`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/) が必要です。
```
-npx @cloudcli-ai/cloudcli sandbox ~/my-project
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
```
Claude Code、Codex、Gemini CLI に対応。詳細は[サンドボックスのドキュメント](docker/)をご覧ください。
diff --git a/README.ko.md b/README.ko.md
index 6cee2adf..f0c17471 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -96,7 +96,7 @@ cloudcli
하이퍼바이저 수준 격리로 에이전트를 샌드박스에서 실행합니다. 기본 에이전트는 Claude Code입니다. [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/)가 필요합니다.
```
-npx @cloudcli-ai/cloudcli sandbox ~/my-project
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
```
Claude Code, Codex, Gemini CLI를 지원합니다. 자세한 내용은 [샌드박스 문서](docker/)를 참고하세요.
diff --git a/README.md b/README.md
index 986505b3..b6f2cfb7 100644
--- a/README.md
+++ b/README.md
@@ -100,7 +100,7 @@ Visit the **[documentation →](https://cloudcli.ai/docs)** for full configurati
Run agents in isolated sandboxes with hypervisor-level isolation. Starts Claude Code by default. Requires the [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/).
```
-npx @cloudcli-ai/cloudcli sandbox ~/my-project
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
```
Supports Claude Code, Codex, and Gemini CLI. See the [sandbox docs](docker/) for setup and advanced options.
@@ -116,7 +116,7 @@ CloudCLI UI is the open source UI layer that powers CloudCLI Cloud. You can self
|---|---|---|---|
| **Best for** | Local agent sessions on your own machine | Isolated agents with web/mobile IDE | Teams who want agents in the cloud |
| **How you access it** | Browser via `[yourip]:port` | Browser via `localhost:port` | Browser, any IDE, REST API, n8n |
-| **Setup** | `npx @cloudcli-ai/cloudcli` | `npx @cloudcli-ai/cloudcli sandbox ~/project` | No setup required |
+| **Setup** | `npx @cloudcli-ai/cloudcli` | `npx @cloudcli-ai/cloudcli@latest sandbox ~/project` | No setup required |
| **Isolation** | Runs on your host | Hypervisor-level sandbox (microVM) | Full cloud isolation |
| **Machine needs to stay on** | Yes | Yes | No |
| **Mobile access** | Any browser on your network | Any browser on your network | Any device, native app coming |
diff --git a/README.ru.md b/README.ru.md
index 55b936a8..a5907b38 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -100,7 +100,7 @@ cloudcli
Запускайте агентов в изолированных песочницах с гипервизорной изоляцией. По умолчанию запускается Claude Code. Требуется [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/).
```
-npx @cloudcli-ai/cloudcli sandbox ~/my-project
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
```
Поддерживаются Claude Code, Codex и Gemini CLI. Подробнее в [документации sandbox](docker/).
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 3f18d3cd..daf9a1a9 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -96,7 +96,7 @@ cloudcli
在隔离的沙箱中运行代理,具有虚拟机管理程序级别的隔离。默认启动 Claude Code。需要 [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/)。
```
-npx @cloudcli-ai/cloudcli sandbox ~/my-project
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
```
支持 Claude Code、Codex 和 Gemini CLI。详情请参阅 [沙箱文档](docker/)。
diff --git a/docker/README.md b/docker/README.md
index c093eccb..134ebad0 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -29,7 +29,7 @@ sbx secret set -g anthropic
### 3. Launch Claude Code
```bash
-npx @cloudcli-ai/cloudcli sandbox ~/my-project
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project
```
Open **http://localhost:3001**. Set a password on first visit. Start building.
@@ -41,11 +41,11 @@ Store the matching API key and pass `--agent`:
```bash
# OpenAI Codex
sbx secret set -g openai
-npx @cloudcli-ai/cloudcli sandbox ~/my-project --agent codex
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project --agent codex
# Gemini CLI
sbx secret set -g google
-npx @cloudcli-ai/cloudcli sandbox ~/my-project --agent gemini
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project --agent gemini
```
### Available templates
@@ -61,11 +61,19 @@ These are used with `--template` when running `sbx` directly (see [Advanced usag
## Managing sandboxes
```bash
-cloudcli sandbox ls # List all sandboxes
-cloudcli sandbox stop my-project # Stop (preserves state)
+sbx ls # List all sandboxes
+sbx stop my-project # Stop (preserves state)
+sbx start my-project # Restart a stopped sandbox
+sbx rm my-project # Remove everything
+sbx exec my-project bash # Open a shell inside the sandbox
+```
+
+If you install CloudCLI globally (`npm install -g @cloudcli-ai/cloudcli`), you can also use:
+
+```bash
+cloudcli sandbox ls
cloudcli sandbox start my-project # Restart and re-launch web UI
cloudcli sandbox logs my-project # View server logs
-cloudcli sandbox rm my-project # Remove everything
```
## What you get
@@ -84,14 +92,20 @@ Your project directory is mounted bidirectionally — edits propagate in real ti
Set variables at creation time with `--env`:
```bash
-npx @cloudcli-ai/cloudcli sandbox ~/my-project --env SERVER_PORT=8080
+npx @cloudcli-ai/cloudcli@latest sandbox ~/my-project --env SERVER_PORT=8080
```
Or inside a running sandbox:
```bash
sbx exec my-project bash -c 'echo "export SERVER_PORT=8080" >> /etc/sandbox-persistent.sh'
-sbx exec my-project bash -c 'pkill -f "server/index.js"; . ~/.cloudcli-start.sh'
+```
+
+Restart CloudCLI for changes to take effect:
+
+```bash
+sbx exec my-project bash -c 'pkill -f "server/index.js"'
+sbx exec -d my-project cloudcli start --port 3001
```
| Variable | Default | Description |
diff --git a/server/cli.js b/server/cli.js
index dad74268..1d847159 100755
--- a/server/cli.js
+++ b/server/cli.js
@@ -448,7 +448,7 @@ async function sandboxCommand(args) {
} catch { /* might already be running */ }
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
- sbx(['exec', '-d', opts.name, 'bash', '-c', '. ~/.cloudcli-start.sh']);
+ sbx(['exec', '-d', opts.name, 'cloudcli', 'start', '--port', '3001']);
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
try {
@@ -548,14 +548,11 @@ async function sandboxCommand(args) {
}
}
- // Step 3: Start CloudCLI
+ // Step 3: Start CloudCLI as a long-running detached exec session.
+ // Using -d with a long-running command (cloudcli start never exits)
+ // keeps the exec session alive, which keeps the sandbox running.
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
- try {
- sbx(['exec', '-d', opts.name, 'bash', '-c', '. ~/.cloudcli-start.sh']);
- } catch (e) {
- console.error(`${c.error('❌')} Failed to start CloudCLI: ${e.message}`);
- process.exit(1);
- }
+ sbx(['exec', '-d', opts.name, 'cloudcli', 'start', '--port', '3001']);
// Step 4: Forward port
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
@@ -582,10 +579,11 @@ async function sandboxCommand(args) {
console.log(`\n${c.ok('✔')} ${c.bright('CloudCLI is ready!')}`);
console.log(` ${c.info('→')} Open ${c.bright(`http://localhost:${opts.port}`)}`);
console.log(`\n${c.dim(' Manage with:')}`);
- console.log(` ${c.dim('$')} cloudcli sandbox ls`);
- console.log(` ${c.dim('$')} cloudcli sandbox stop ${opts.name}`);
- console.log(` ${c.dim('$')} cloudcli sandbox start ${opts.name}`);
- console.log(` ${c.dim('$')} cloudcli sandbox rm ${opts.name}\n`);
+ console.log(` ${c.dim('$')} sbx ls`);
+ console.log(` ${c.dim('$')} sbx stop ${opts.name}`);
+ console.log(` ${c.dim('$')} sbx start ${opts.name}`);
+ console.log(` ${c.dim('$')} sbx rm ${opts.name}`);
+ console.log(`\n${c.dim(' Or install globally:')} npm install -g @cloudcli-ai/cloudcli\n`);
break;
}
From b6d19201b6ee86975b4abdb40a3b22b494b0127e Mon Sep 17 00:00:00 2001
From: viper151
Date: Tue, 14 Apr 2026 17:38:53 +0000
Subject: [PATCH 10/14] chore(release): v1.29.1
---
CHANGELOG.md | 6 ++++++
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index daac93a0..87e6848f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@
All notable changes to CloudCLI UI will be documented in this file.
+## [1.29.1](https://github.com/siteboon/claudecodeui/compare/v1.29.0...v1.29.1) (2026-04-14)
+
+### Bug Fixes
+
+* add latest tag to docker npx command and change the detach mode to work without spawn ([4a56972](https://github.com/siteboon/claudecodeui/commit/4a569725dae320a505753359d8edfd8ca79f0fd7))
+
## [1.29.0](https://github.com/siteboon/claudecodeui/compare/v1.28.1...v1.29.0) (2026-04-14)
### New Features
diff --git a/package-lock.json b/package-lock.json
index 192b06eb..1a78cb27 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@cloudcli-ai/cloudcli",
- "version": "1.29.0",
+ "version": "1.29.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@cloudcli-ai/cloudcli",
- "version": "1.29.0",
+ "version": "1.29.1",
"hasInstallScript": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
diff --git a/package.json b/package.json
index 1b94330e..521a634b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@cloudcli-ai/cloudcli",
- "version": "1.29.0",
+ "version": "1.29.1",
"description": "A web-based UI for Claude Code CLI",
"type": "module",
"main": "server/index.js",
From 9b11c034d9a19710a23b56c62dcf07c21a17bd97 Mon Sep 17 00:00:00 2001
From: simosmik
Date: Tue, 14 Apr 2026 18:14:58 +0000
Subject: [PATCH 11/14] fix(sandbox): use backgrounded sbx run to keep sandbox
alive
---
server/cli.js | 46 ++++++++++++++++++++++------------------------
1 file changed, 22 insertions(+), 24 deletions(-)
diff --git a/server/cli.js b/server/cli.js
index 1d847159..3c0d1d4f 100755
--- a/server/cli.js
+++ b/server/cli.js
@@ -367,7 +367,7 @@ Advanced usage:
}
async function sandboxCommand(args) {
- const { execFileSync } = await import('child_process');
+ const { execFileSync, spawn: spawnProcess } = await import('child_process');
// Safe execution — uses execFileSync (no shell) to prevent injection
const sbx = (subcmd, opts = {}) => {
@@ -443,12 +443,15 @@ async function sandboxCommand(args) {
process.exit(1);
}
console.log(`\n${c.info('▶')} Starting sandbox ${c.bright(opts.name)}...`);
- try {
- sbx(['start', opts.name], { inherit: true });
- } catch { /* might already be running */ }
+ const restartRun = spawnProcess('sbx', ['run', opts.name], {
+ detached: true,
+ stdio: ['ignore', 'ignore', 'ignore'],
+ });
+ restartRun.unref();
+ await new Promise(resolve => setTimeout(resolve, 5000));
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
- sbx(['exec', '-d', opts.name, 'cloudcli', 'start', '--port', '3001']);
+ sbx(['exec', opts.name, 'bash', '-c', 'cloudcli start --port 3001 &']);
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
try {
@@ -515,22 +518,19 @@ async function sandboxCommand(args) {
}
console.log(c.dim('─'.repeat(50)));
- // Step 1: Create sandbox
+ // Step 1: Launch sandbox with sbx run in background.
+ // sbx run creates the sandbox (or reconnects) AND holds an active session,
+ // which prevents the sandbox from auto-stopping.
console.log(`\n${c.info('▶')} Creating sandbox ${c.bright(opts.name)}...`);
- try {
- sbx(
- ['create', '--template', opts.template, '--name', opts.name, opts.agent, workspace],
- { inherit: true }
- );
- } catch (e) {
- const msg = e.stdout || e.stderr || e.message || '';
- if (msg.includes('already exists')) {
- console.log(`${c.warn('⚠')} Sandbox ${c.bright(opts.name)} already exists. Starting it instead...\n`);
- try { sbx(['start', opts.name]); } catch { /* may already be running */ }
- } else {
- throw e;
- }
- }
+ const bgRun = spawnProcess('sbx', [
+ 'run', '--template', opts.template, '--name', opts.name, opts.agent, workspace,
+ ], {
+ detached: true,
+ stdio: ['ignore', 'ignore', 'ignore'],
+ });
+ bgRun.unref();
+ // Wait for sandbox to be ready
+ await new Promise(resolve => setTimeout(resolve, 5000));
// Step 2: Inject environment variables
if (opts.env.length > 0) {
@@ -548,11 +548,9 @@ async function sandboxCommand(args) {
}
}
- // Step 3: Start CloudCLI as a long-running detached exec session.
- // Using -d with a long-running command (cloudcli start never exits)
- // keeps the exec session alive, which keeps the sandbox running.
+ // Step 3: Start CloudCLI inside the sandbox
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
- sbx(['exec', '-d', opts.name, 'cloudcli', 'start', '--port', '3001']);
+ sbx(['exec', opts.name, 'bash', '-c', 'cloudcli start --port 3001 &']);
// Step 4: Forward port
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
From c3599cd2c48048c969a10bcefcb16c4caf20c57b Mon Sep 17 00:00:00 2001
From: viper151
Date: Tue, 14 Apr 2026 18:16:20 +0000
Subject: [PATCH 12/14] chore(release): v1.29.2
---
CHANGELOG.md | 6 ++++++
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87e6848f..149443d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@
All notable changes to CloudCLI UI will be documented in this file.
+## [1.29.2](https://github.com/siteboon/claudecodeui/compare/v1.29.1...v1.29.2) (2026-04-14)
+
+### Bug Fixes
+
+* **sandbox:** use backgrounded sbx run to keep sandbox alive ([9b11c03](https://github.com/siteboon/claudecodeui/commit/9b11c034d9a19710a23b56c62dcf07c21a17bd97))
+
## [1.29.1](https://github.com/siteboon/claudecodeui/compare/v1.29.0...v1.29.1) (2026-04-14)
### Bug Fixes
diff --git a/package-lock.json b/package-lock.json
index 1a78cb27..21bee5b6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@cloudcli-ai/cloudcli",
- "version": "1.29.1",
+ "version": "1.29.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@cloudcli-ai/cloudcli",
- "version": "1.29.1",
+ "version": "1.29.2",
"hasInstallScript": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
diff --git a/package.json b/package.json
index 521a634b..2a80d5a3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@cloudcli-ai/cloudcli",
- "version": "1.29.1",
+ "version": "1.29.2",
"description": "A web-based UI for Claude Code CLI",
"type": "module",
"main": "server/index.js",
From 641304242d7705b54aab65faa4a7673438c92c60 Mon Sep 17 00:00:00 2001
From: Haile <118998054+blackmammoth@users.noreply.github.com>
Date: Wed, 15 Apr 2026 00:02:20 +0300
Subject: [PATCH 13/14] fix(version-upgrade-modal): implement reload countdown
and update UI messages (#655)
Co-authored-by: Haileyesus
---
.../view/VersionUpgradeModal.tsx | 34 +++++++++++++++++--
1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/src/components/version-upgrade/view/VersionUpgradeModal.tsx b/src/components/version-upgrade/view/VersionUpgradeModal.tsx
index c9be12d2..cf6b94b7 100644
--- a/src/components/version-upgrade/view/VersionUpgradeModal.tsx
+++ b/src/components/version-upgrade/view/VersionUpgradeModal.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useState } from "react";
+import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { authenticatedFetch } from "../../../utils/api";
import { ReleaseInfo } from "../../../types/sharedTypes";
@@ -15,6 +15,8 @@ interface VersionUpgradeModalProps {
installMode: InstallMode;
}
+const RELOAD_COUNTDOWN_START = 30;
+
export function VersionUpgradeModal({
isOpen,
onClose,
@@ -32,10 +34,30 @@ export function VersionUpgradeModal({
const [isUpdating, setIsUpdating] = useState(false);
const [updateOutput, setUpdateOutput] = useState('');
const [updateError, setUpdateError] = useState('');
+ const [reloadCountdown, setReloadCountdown] = useState(null);
+
+ useEffect(() => {
+ if (!IS_PLATFORM || reloadCountdown === null || reloadCountdown <= 0) {
+ return;
+ }
+
+ const timeoutId = window.setTimeout(() => {
+ setReloadCountdown((previousCountdown) => {
+ if (previousCountdown === null) {
+ return null;
+ }
+
+ return Math.max(previousCountdown - 1, 0);
+ });
+ }, 1000);
+
+ return () => window.clearTimeout(timeoutId);
+ }, [reloadCountdown]);
const handleUpdateNow = useCallback(async () => {
setIsUpdating(true);
setUpdateOutput('Starting update...\n');
+ setReloadCountdown(IS_PLATFORM ? RELOAD_COUNTDOWN_START : null);
setUpdateError('');
try {
@@ -49,8 +71,7 @@ export function VersionUpgradeModal({
if (response.ok) {
setUpdateOutput(prev => prev + data.output + '\n');
setUpdateOutput(prev => prev + '\n✅ Update completed successfully!\n');
- const text = IS_PLATFORM ? 'Please refresh the page after 5 seconds to load the new version. If that doesn\'t work, RESTART the environment.' : 'Please restart the server to apply changes.';
- setUpdateOutput(prev => prev + text + '\n');
+ setUpdateOutput(prev => prev + 'Please restart the server to apply changes.' + '\n');
} else {
setUpdateError(data.error || 'Update failed');
setUpdateOutput(prev => prev + '\n❌ Update failed: ' + (data.error || 'Unknown error') + '\n');
@@ -147,6 +168,13 @@ export function VersionUpgradeModal({
{updateOutput}
+ {IS_PLATFORM && reloadCountdown !== null && (
+
+ {reloadCountdown === 0
+ ? 'Refresh the page now. If that doesn\'t work, RESTART the environment.'
+ : `Refresh the page in ${reloadCountdown} ${reloadCountdown === 1 ? 'second' : 'seconds'}. If that doesn\'t work, RESTART the environment.`}
+