AI coding CLIs are powerful, but running them directly on a developer machine or production server introduces real risks: unconstrained filesystem access, network egress, and accidental side effects from agent-generated commands. OpenSandbox solves this by giving each CLI its own isolated container. API keys are injected as environment variables at sandbox creation time and never written to disk, and the sandbox is terminated after the task completes. TheDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/opensandbox-group/OpenSandbox/llms.txt
Use this file to discover all available pages before exploring further.
code-interpreter image is the recommended base because it ships with Node.js and Python 3.12+ pre-installed, which all five CLIs below require.
Prerequisites
Pull the image and start the server before running any example:docker pull sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.1.0
uv pip install opensandbox-server
opensandbox-server init-config ~/.sandbox.toml --example docker
opensandbox-server
uv pip install opensandbox
- Claude Code
- Gemini CLI
- Codex CLI
- Qwen Code
- Kimi CLI
Claude Code is Anthropic’s agentic coding assistant. The CLI is installed at runtime via npm; the auth token is passed through the container environment so it is never written to the repository.
Run the example:
Environment variables
| Variable | Default | Description |
|---|---|---|
SANDBOX_DOMAIN | localhost:8080 | Sandbox service address |
SANDBOX_API_KEY | (optional) | API key if your server requires authentication |
SANDBOX_IMAGE | sandbox-registry…/code-interpreter:v1.1.0 | Sandbox image |
ANTHROPIC_AUTH_TOKEN | (required) | Your Anthropic auth token |
ANTHROPIC_BASE_URL | (optional) | API endpoint override |
ANTHROPIC_MODEL | claude_sonnet4 | Model name |
Example
import asyncio
import os
from datetime import timedelta
from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig
def _required_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"{name} is required")
return value
async def _print_execution_logs(execution) -> None:
for msg in execution.logs.stdout:
print(f"[stdout] {msg.text}")
for msg in execution.logs.stderr:
print(f"[stderr] {msg.text}")
if execution.error:
print(f"[error] {execution.error.name}: {execution.error.value}")
async def main() -> None:
domain = os.getenv("SANDBOX_DOMAIN", "localhost:8080")
api_key = os.getenv("SANDBOX_API_KEY")
claude_auth_token = _required_env("ANTHROPIC_AUTH_TOKEN")
claude_base_url = os.getenv("ANTHROPIC_BASE_URL")
claude_model_name = os.getenv("ANTHROPIC_MODEL", "claude_sonnet4")
image = os.getenv(
"SANDBOX_IMAGE",
"sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.1.0",
)
config = ConnectionConfig(
domain=domain,
api_key=api_key,
request_timeout=timedelta(seconds=60),
)
# Inject Claude settings into container environment for CLI access
env = {
"ANTHROPIC_AUTH_TOKEN": claude_auth_token,
"ANTHROPIC_BASE_URL": claude_base_url,
"ANTHROPIC_MODEL": claude_model_name,
"IS_SANDBOX": "1",
}
env = {k: v for k, v in env.items() if v is not None}
sandbox = await Sandbox.create(
image,
connection_config=config,
env=env,
)
async with sandbox:
# Install Claude CLI (Node.js is already in the code-interpreter image)
install_exec = await sandbox.commands.run(
"npm i -g @anthropic-ai/claude-code@latest"
)
await _print_execution_logs(install_exec)
# Use Claude CLI to send a message
run_exec = await sandbox.commands.run(
'claude "Compute 1+1=?."'
)
await _print_execution_logs(run_exec)
await sandbox.kill()
if __name__ == "__main__":
asyncio.run(main())
ANTHROPIC_AUTH_TOKEN=your-token uv run python examples/claude-code/main.py
Gemini CLI (
Run the example:
@google/gemini-cli) gives agents access to Google’s Gemini models. The API key is injected as an environment variable and the CLI is installed at sandbox start.Environment variables
| Variable | Default | Description |
|---|---|---|
SANDBOX_DOMAIN | localhost:8080 | Sandbox service address |
SANDBOX_API_KEY | (optional) | API key if your server requires authentication |
SANDBOX_IMAGE | sandbox-registry…/code-interpreter:v1.1.0 | Sandbox image |
GEMINI_API_KEY | (required) | Your Google Gemini API key |
GEMINI_BASE_URL | (optional) | API endpoint override |
GEMINI_MODEL | gemini-2.5-flash | Model to use |
Example
import asyncio
import os
from datetime import timedelta
from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig
def _required_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"{name} is required")
return value
async def _print_execution_logs(execution) -> None:
for msg in execution.logs.stdout:
print(f"[stdout] {msg.text}")
for msg in execution.logs.stderr:
print(f"[stderr] {msg.text}")
if execution.error:
print(f"[error] {execution.error.name}: {execution.error.value}")
async def main() -> None:
domain = os.getenv("SANDBOX_DOMAIN", "localhost:8080")
api_key = os.getenv("SANDBOX_API_KEY")
gemini_api_key = _required_env("GEMINI_API_KEY")
gemini_base_url = os.getenv("GEMINI_BASE_URL")
gemini_model = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")
image = os.getenv(
"SANDBOX_IMAGE",
"sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.1.0",
)
config = ConnectionConfig(
domain=domain,
api_key=api_key,
request_timeout=timedelta(seconds=60),
)
env = {
"GEMINI_API_KEY": gemini_api_key,
"GEMINI_BASE_URL": gemini_base_url,
"GEMINI_MODEL": gemini_model,
}
env = {k: v for k, v in env.items() if v is not None}
sandbox = await Sandbox.create(
image,
connection_config=config,
env=env,
)
async with sandbox:
# Install Gemini CLI (Node.js is already in the code-interpreter image)
install_exec = await sandbox.commands.run(
"npm install -g @google/gemini-cli@latest"
)
await _print_execution_logs(install_exec)
# Use Gemini CLI to send a message
run_exec = await sandbox.commands.run(
'gemini "Compute 1+1=?."'
)
await _print_execution_logs(run_exec)
await sandbox.kill()
if __name__ == "__main__":
asyncio.run(main())
GEMINI_API_KEY=your-key uv run python examples/gemini-cli/main.py
OpenAI Codex CLI (
Run the example:
@openai/codex) uses the codex exec sub-command to run a task and return structured output. It supports any OpenAI-compatible endpoint via OPENAI_BASE_URL.Environment variables
| Variable | Default | Description |
|---|---|---|
SANDBOX_DOMAIN | localhost:8080 | Sandbox service address |
SANDBOX_API_KEY | (optional) | API key if your server requires authentication |
SANDBOX_IMAGE | sandbox-registry…/code-interpreter:v1.1.0 | Sandbox image |
OPENAI_API_KEY | (required) | Your OpenAI API key |
OPENAI_BASE_URL | https://api.openai.com/v1 | OpenAI API endpoint |
OPENAI_MODEL | gpt-4o-mini | Model to use |
Example
import asyncio
import os
from datetime import timedelta
from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig
def _required_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"{name} is required")
return value
async def _print_execution_logs(execution) -> None:
for msg in execution.logs.stdout:
print(f"[stdout] {msg.text}")
for msg in execution.logs.stderr:
print(f"[stderr] {msg.text}")
if execution.error:
print(f"[error] {execution.error.name}: {execution.error.value}")
async def main() -> None:
domain = os.getenv("SANDBOX_DOMAIN", "localhost:8080")
api_key = os.getenv("SANDBOX_API_KEY")
openai_api_key = _required_env("OPENAI_API_KEY")
openai_base_url = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
openai_model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
image = os.getenv(
"SANDBOX_IMAGE",
"sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.1.0",
)
config = ConnectionConfig(
domain=domain,
api_key=api_key,
request_timeout=timedelta(seconds=60),
)
env = {
"OPENAI_API_KEY": openai_api_key,
"OPENAI_BASE_URL": openai_base_url,
"OPENAI_MODEL": openai_model,
}
env = {k: v for k, v in env.items() if v is not None}
sandbox = await Sandbox.create(
image,
connection_config=config,
env=env,
)
async with sandbox:
# Install Codex CLI (Node.js is already in the code-interpreter image)
install_exec = await sandbox.commands.run(
"npm install -g @openai/codex@latest"
)
await _print_execution_logs(install_exec)
# Use Codex CLI to execute a command
run_exec = await sandbox.commands.run(
'codex exec "Compute 1+1=?." --skip-git-repo-check'
)
await _print_execution_logs(run_exec)
await sandbox.kill()
if __name__ == "__main__":
asyncio.run(main())
OPENAI_API_KEY=your-key uv run python examples/codex-cli/main.py
Qwen Code (
Run the example:
@qwen-code/qwen-code) uses an OpenAI-compatible API and reads provider configuration from a project-local .qwen/settings.json file. The script generates this config inside the sandbox, keeping credentials out of the repository entirely.Environment variables
| Variable | Default | Description |
|---|---|---|
SANDBOX_DOMAIN | localhost:8080 | Sandbox service address |
SANDBOX_API_KEY | (optional) | API key if your server requires authentication |
SANDBOX_IMAGE | sandbox-registry…/code-interpreter:v1.1.0 | Sandbox image |
API_KEY | (required) | API key for the OpenAI-compatible provider |
BASE_URL | https://dashscope.aliyuncs.com/compatible-mode/v1 | OpenAI-compatible base URL |
MODEL_NAME | qwen3-coder-plus | Model name |
Example
import asyncio
import json
import os
from datetime import timedelta
from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig
from opensandbox.models.filesystem import WriteEntry
QWEN_PROJECT_DIR = "/tmp/qwen-code-example"
QWEN_SETTINGS_DIR = f"{QWEN_PROJECT_DIR}/.qwen"
QWEN_SETTINGS_PATH = f"{QWEN_SETTINGS_DIR}/settings.json"
def _required_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"{name} is required")
return value
def _build_qwen_settings(base_url: str, model_name: str) -> str:
settings = {
"modelProviders": {
"openai": [
{
"id": model_name,
"name": model_name,
"baseUrl": base_url,
"description": "Qwen Code via OpenAI-compatible API in OpenSandbox",
"envKey": "API_KEY",
}
]
},
"security": {"auth": {"selectedType": "openai"}},
"model": {"name": model_name},
}
return json.dumps(settings, indent=2)
async def _print_execution_logs(execution) -> None:
for msg in execution.logs.stdout:
print(f"[stdout] {msg.text}")
for msg in execution.logs.stderr:
print(f"[stderr] {msg.text}")
if execution.error:
print(f"[error] {execution.error.name}: {execution.error.value}")
async def main() -> None:
domain = os.getenv("SANDBOX_DOMAIN", "localhost:8080")
api_key = os.getenv("SANDBOX_API_KEY")
qwen_api_key = _required_env("API_KEY")
qwen_base_url = os.getenv("BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
qwen_model_name = os.getenv("MODEL_NAME", "qwen3-coder-plus")
image = os.getenv(
"SANDBOX_IMAGE",
"sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.1.0",
)
config = ConnectionConfig(
domain=domain,
api_key=api_key,
request_timeout=timedelta(seconds=60),
)
sandbox = await Sandbox.create(
image,
connection_config=config,
env={"API_KEY": qwen_api_key},
)
async with sandbox:
await sandbox.files.create_directories(
[
WriteEntry(path=QWEN_PROJECT_DIR, mode=755),
WriteEntry(path=QWEN_SETTINGS_DIR, mode=755),
]
)
await sandbox.files.write_file(
QWEN_SETTINGS_PATH,
_build_qwen_settings(qwen_base_url, qwen_model_name),
mode=644,
)
# Install Qwen Code CLI (Node.js is already in the code-interpreter image)
install_exec = await sandbox.commands.run(
"npm install -g @qwen-code/qwen-code@latest"
)
await _print_execution_logs(install_exec)
# Run Qwen Code in headless mode using the project-local config
run_exec = await sandbox.commands.run(
'cd /tmp/qwen-code-example && qwen -p "Compute 1+1 and reply with only the final number."'
)
await _print_execution_logs(run_exec)
await sandbox.kill()
if __name__ == "__main__":
asyncio.run(main())
API_KEY=your-key uv run python examples/qwen-code/main.py
Kimi Code CLI (
Run the example:
kimi-cli) from Moonshot AI is installed via pip at runtime. The API key is injected as an environment variable; a project-local .qwen/settings.json is not required because Kimi CLI reads credentials directly from KIMI_API_KEY.Environment variables
| Variable | Default | Description |
|---|---|---|
SANDBOX_DOMAIN | localhost:8080 | Sandbox service address |
SANDBOX_API_KEY | (optional) | API key if your server requires authentication |
SANDBOX_IMAGE | sandbox-registry…/code-interpreter:v1.1.0 | Sandbox image |
KIMI_API_KEY | (required) | Your Moonshot AI / Kimi API key |
KIMI_BASE_URL | (optional) | Kimi API endpoint override |
KIMI_MODEL_NAME | kimi-k2.5 | Model to use |
Example
import asyncio
import os
from datetime import timedelta
from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig
def _required_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"{name} is required")
return value
async def _print_execution_logs(execution) -> None:
for msg in execution.logs.stdout:
print(f"[stdout] {msg.text}")
for msg in execution.logs.stderr:
print(f"[stderr] {msg.text}")
if execution.error:
print(f"[error] {execution.error.name}: {execution.error.value}")
async def main() -> None:
domain = os.getenv("SANDBOX_DOMAIN", "localhost:8080")
api_key = os.getenv("SANDBOX_API_KEY")
kimi_api_key = _required_env("KIMI_API_KEY")
kimi_base_url = os.getenv("KIMI_BASE_URL")
kimi_model_name = os.getenv("KIMI_MODEL_NAME", "kimi-k2.5")
image = os.getenv(
"SANDBOX_IMAGE",
"sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.1.0",
)
config = ConnectionConfig(
domain=domain,
api_key=api_key,
request_timeout=timedelta(seconds=60),
)
# Inject Kimi settings into container environment for CLI access
env = {
"KIMI_API_KEY": kimi_api_key,
"KIMI_BASE_URL": kimi_base_url,
"KIMI_MODEL_NAME": kimi_model_name,
}
# Drop None values to avoid overriding defaults inside CLI
env = {k: v for k, v in env.items() if v is not None}
sandbox = await Sandbox.create(
image,
connection_config=config,
env=env,
)
async with sandbox:
# Install Kimi CLI (Python 3.12+ is already in the code-interpreter image)
install_exec = await sandbox.commands.run(
"pip install kimi-cli"
)
await _print_execution_logs(install_exec)
# Use Kimi CLI to send a message in non-interactive mode
run_exec = await sandbox.commands.run(
'kimi -p "Compute 1+1=?."'
)
await _print_execution_logs(run_exec)
await sandbox.kill()
if __name__ == "__main__":
asyncio.run(main())
KIMI_API_KEY=your-key uv run python examples/kimi-cli/main.py