mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-15 10:01:31 +00:00
Compare commits
9 Commits
v1.29.0
...
fix/replac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c85ddc807 | ||
|
|
8ff5f35c05 | ||
|
|
641304242d | ||
|
|
c3599cd2c4 | ||
|
|
9b11c034d9 | ||
|
|
b6d19201b6 | ||
|
|
4a569725da | ||
|
|
3bbd56e8e9 | ||
|
|
11733918e5 |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to CloudCLI UI will be documented in this file.
|
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
|
||||||
|
|
||||||
|
* 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)
|
## [1.29.0](https://github.com/siteboon/claudecodeui/compare/v1.28.1...v1.29.0) (2026-04-14)
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|||||||
@@ -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/).
|
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/).
|
Unterstützt Claude Code, Codex und Gemini CLI. Weitere Details in der [Sandbox-Dokumentation](docker/).
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ cloudcli
|
|||||||
ハイパーバイザーレベルの分離でエージェントをサンドボックスで実行します。デフォルトでは Claude Code が起動します。[`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/) が必要です。
|
ハイパーバイザーレベルの分離でエージェントをサンドボックスで実行します。デフォルトでは 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/)をご覧ください。
|
Claude Code、Codex、Gemini CLI に対応。詳細は[サンドボックスのドキュメント](docker/)をご覧ください。
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ cloudcli
|
|||||||
하이퍼바이저 수준 격리로 에이전트를 샌드박스에서 실행합니다. 기본 에이전트는 Claude Code입니다. [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/)가 필요합니다.
|
하이퍼바이저 수준 격리로 에이전트를 샌드박스에서 실행합니다. 기본 에이전트는 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/)를 참고하세요.
|
Claude Code, Codex, Gemini CLI를 지원합니다. 자세한 내용은 [샌드박스 문서](docker/)를 참고하세요.
|
||||||
|
|||||||
@@ -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/).
|
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.
|
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 |
|
| **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 |
|
| **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 |
|
| **Isolation** | Runs on your host | Hypervisor-level sandbox (microVM) | Full cloud isolation |
|
||||||
| **Machine needs to stay on** | Yes | Yes | No |
|
| **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 |
|
| **Mobile access** | Any browser on your network | Any browser on your network | Any device, native app coming |
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ cloudcli
|
|||||||
Запускайте агентов в изолированных песочницах с гипервизорной изоляцией. По умолчанию запускается Claude Code. Требуется [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/).
|
Запускайте агентов в изолированных песочницах с гипервизорной изоляцией. По умолчанию запускается 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/).
|
Поддерживаются Claude Code, Codex и Gemini CLI. Подробнее в [документации sandbox](docker/).
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ cloudcli
|
|||||||
在隔离的沙箱中运行代理,具有虚拟机管理程序级别的隔离。默认启动 Claude Code。需要 [`sbx` CLI](https://docs.docker.com/ai/sandboxes/get-started/)。
|
在隔离的沙箱中运行代理,具有虚拟机管理程序级别的隔离。默认启动 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/)。
|
支持 Claude Code、Codex 和 Gemini CLI。详情请参阅 [沙箱文档](docker/)。
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ sbx secret set -g anthropic
|
|||||||
### 3. Launch Claude Code
|
### 3. Launch Claude Code
|
||||||
|
|
||||||
```bash
|
```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.
|
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
|
```bash
|
||||||
# OpenAI Codex
|
# OpenAI Codex
|
||||||
sbx secret set -g openai
|
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
|
# Gemini CLI
|
||||||
sbx secret set -g google
|
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
|
### Available templates
|
||||||
@@ -61,11 +61,19 @@ These are used with `--template` when running `sbx` directly (see [Advanced usag
|
|||||||
## Managing sandboxes
|
## Managing sandboxes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cloudcli sandbox ls # List all sandboxes
|
sbx ls # List all sandboxes
|
||||||
cloudcli sandbox stop my-project # Stop (preserves state)
|
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 start my-project # Restart and re-launch web UI
|
||||||
cloudcli sandbox logs my-project # View server logs
|
cloudcli sandbox logs my-project # View server logs
|
||||||
cloudcli sandbox rm my-project # Remove everything
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## What you get
|
## 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`:
|
Set variables at creation time with `--env`:
|
||||||
|
|
||||||
```bash
|
```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:
|
Or inside a running sandbox:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sbx exec my-project bash -c 'echo "export SERVER_PORT=8080" >> /etc/sandbox-persistent.sh'
|
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 |
|
| Variable | Default | Description |
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@cloudcli-ai/cloudcli",
|
"name": "@cloudcli-ai/cloudcli",
|
||||||
"version": "1.29.0",
|
"version": "1.29.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@cloudcli-ai/cloudcli",
|
"name": "@cloudcli-ai/cloudcli",
|
||||||
"version": "1.29.0",
|
"version": "1.29.2",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@cloudcli-ai/cloudcli",
|
"name": "@cloudcli-ai/cloudcli",
|
||||||
"version": "1.29.0",
|
"version": "1.29.2",
|
||||||
"description": "A web-based UI for Claude Code CLI",
|
"description": "A web-based UI for Claude Code CLI",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "server/index.js",
|
"main": "server/index.js",
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ Advanced usage:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sandboxCommand(args) {
|
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
|
// Safe execution — uses execFileSync (no shell) to prevent injection
|
||||||
const sbx = (subcmd, opts = {}) => {
|
const sbx = (subcmd, opts = {}) => {
|
||||||
@@ -443,12 +443,15 @@ async function sandboxCommand(args) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log(`\n${c.info('▶')} Starting sandbox ${c.bright(opts.name)}...`);
|
console.log(`\n${c.info('▶')} Starting sandbox ${c.bright(opts.name)}...`);
|
||||||
try {
|
const restartRun = spawnProcess('sbx', ['run', opts.name], {
|
||||||
sbx(['start', opts.name], { inherit: true });
|
detached: true,
|
||||||
} catch { /* might already be running */ }
|
stdio: ['ignore', 'ignore', 'ignore'],
|
||||||
|
});
|
||||||
|
restartRun.unref();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
|
||||||
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
|
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
|
||||||
sbx(['exec', '-d', opts.name, 'bash', '-c', '. ~/.cloudcli-start.sh']);
|
sbx(['exec', opts.name, 'bash', '-c', 'cloudcli start --port 3001 &']);
|
||||||
|
|
||||||
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
|
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
|
||||||
try {
|
try {
|
||||||
@@ -515,22 +518,19 @@ async function sandboxCommand(args) {
|
|||||||
}
|
}
|
||||||
console.log(c.dim('─'.repeat(50)));
|
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)}...`);
|
console.log(`\n${c.info('▶')} Creating sandbox ${c.bright(opts.name)}...`);
|
||||||
try {
|
const bgRun = spawnProcess('sbx', [
|
||||||
sbx(
|
'run', '--template', opts.template, '--name', opts.name, opts.agent, workspace,
|
||||||
['create', '--template', opts.template, '--name', opts.name, opts.agent, workspace],
|
], {
|
||||||
{ inherit: true }
|
detached: true,
|
||||||
);
|
stdio: ['ignore', 'ignore', 'ignore'],
|
||||||
} catch (e) {
|
});
|
||||||
const msg = e.stdout || e.stderr || e.message || '';
|
bgRun.unref();
|
||||||
if (msg.includes('already exists')) {
|
// Wait for sandbox to be ready
|
||||||
console.log(`${c.warn('⚠')} Sandbox ${c.bright(opts.name)} already exists. Starting it instead...\n`);
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
try { sbx(['start', opts.name]); } catch { /* may already be running */ }
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Inject environment variables
|
// Step 2: Inject environment variables
|
||||||
if (opts.env.length > 0) {
|
if (opts.env.length > 0) {
|
||||||
@@ -548,14 +548,9 @@ async function sandboxCommand(args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Start CloudCLI
|
// Step 3: Start CloudCLI inside the sandbox
|
||||||
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
|
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
|
||||||
try {
|
sbx(['exec', opts.name, 'bash', '-c', 'cloudcli start --port 3001 &']);
|
||||||
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
|
// Step 4: Forward port
|
||||||
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
|
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
|
||||||
@@ -582,10 +577,11 @@ async function sandboxCommand(args) {
|
|||||||
console.log(`\n${c.ok('✔')} ${c.bright('CloudCLI is ready!')}`);
|
console.log(`\n${c.ok('✔')} ${c.bright('CloudCLI is ready!')}`);
|
||||||
console.log(` ${c.info('→')} Open ${c.bright(`http://localhost:${opts.port}`)}`);
|
console.log(` ${c.info('→')} Open ${c.bright(`http://localhost:${opts.port}`)}`);
|
||||||
console.log(`\n${c.dim(' Manage with:')}`);
|
console.log(`\n${c.dim(' Manage with:')}`);
|
||||||
console.log(` ${c.dim('$')} cloudcli sandbox ls`);
|
console.log(` ${c.dim('$')} sbx ls`);
|
||||||
console.log(` ${c.dim('$')} cloudcli sandbox stop ${opts.name}`);
|
console.log(` ${c.dim('$')} sbx stop ${opts.name}`);
|
||||||
console.log(` ${c.dim('$')} cloudcli sandbox start ${opts.name}`);
|
console.log(` ${c.dim('$')} sbx start ${opts.name}`);
|
||||||
console.log(` ${c.dim('$')} cloudcli sandbox rm ${opts.name}\n`);
|
console.log(` ${c.dim('$')} sbx rm ${opts.name}`);
|
||||||
|
console.log(`\n${c.dim(' Or install globally:')} npm install -g @cloudcli-ai/cloudcli\n`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import crossSpawn from 'cross-spawn';
|
|
||||||
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
||||||
import { cursorAdapter } from './providers/cursor/adapter.js';
|
import { cursorAdapter } from './providers/cursor/adapter.js';
|
||||||
import { createNormalizedMessage } from './providers/types.js';
|
import { createNormalizedMessage } from './providers/types.js';
|
||||||
|
|
||||||
// Use cross-spawn on Windows for better command execution
|
|
||||||
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
|
||||||
|
|
||||||
let activeCursorProcesses = new Map(); // Track active processes by session ID
|
let activeCursorProcesses = new Map(); // Track active processes by session ID
|
||||||
|
|
||||||
const WORKSPACE_TRUST_PATTERNS = [
|
const WORKSPACE_TRUST_PATTERNS = [
|
||||||
@@ -122,7 +118,7 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
console.log('Working directory:', workingDir);
|
console.log('Working directory:', workingDir);
|
||||||
console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume);
|
console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume);
|
||||||
|
|
||||||
const cursorProcess = spawnFunction('cursor-agent', args, {
|
const cursorProcess = spawn('cursor-agent', args, {
|
||||||
cwd: workingDir,
|
cwd: workingDir,
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
env: { ...process.env } // Inherit all environment variables
|
env: { ...process.env } // Inherit all environment variables
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import crossSpawn from 'cross-spawn';
|
|
||||||
|
|
||||||
// Use cross-spawn on Windows for correct .cmd resolution (same pattern as cursor-cli.js)
|
|
||||||
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
@@ -168,7 +164,7 @@ async function spawnGemini(command, options = {}, ws) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const geminiProcess = spawnFunction(spawnCmd, spawnArgs, {
|
const geminiProcess = spawn(spawnCmd, spawnArgs, {
|
||||||
cwd: workingDir,
|
cwd: workingDir,
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
env: { ...process.env } // Inherit all environment variables
|
env: { ...process.env } // Inherit all environment variables
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import os from 'os';
|
|||||||
import http from 'http';
|
import http from 'http';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import pty from 'node-pty';
|
import pty from 'node-pty';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import mime from 'mime-types';
|
import mime from 'mime-types';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import express from 'express';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import sqlite3 from 'sqlite3';
|
import sqlite3 from 'sqlite3';
|
||||||
import { open } from 'sqlite';
|
import { open } from 'sqlite';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
@@ -795,4 +795,4 @@ router.get('/sessions/:sessionId', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { extractProjectDirectory } from '../projects.js';
|
import { extractProjectDirectory } from '../projects.js';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from 'path';
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
@@ -549,4 +549,4 @@ function parseClaudeGetOutput(output) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { addProjectManually } from '../projects.js';
|
import { addProjectManually } from '../projects.js';
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import express from 'express';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import express from 'express';
|
|||||||
import { userDb } from '../database/db.js';
|
import { userDb } from '../database/db.js';
|
||||||
import { authenticateToken } from '../middleware/auth.js';
|
import { authenticateToken } from '../middleware/auth.js';
|
||||||
import { getSystemGitConfig } from '../utils/gitConfig.js';
|
import { getSystemGitConfig } from '../utils/gitConfig.js';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
|
|
||||||
function spawnAsync(command, args) {
|
function spawnAsync(command, args) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
|
|
||||||
const PLUGINS_DIR = path.join(os.homedir(), '.claude-code-ui', 'plugins');
|
const PLUGINS_DIR = path.join(os.homedir(), '.claude-code-ui', 'plugins');
|
||||||
const PLUGINS_CONFIG_PATH = path.join(os.homedir(), '.claude-code-ui', 'plugins.json');
|
const PLUGINS_CONFIG_PATH = path.join(os.homedir(), '.claude-code-ui', 'plugins.json');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'cross-spawn';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { scanPlugins, getPluginsConfig, getPluginDir } from './plugin-loader.js';
|
import { scanPlugins, getPluginsConfig, getPluginDir } from './plugin-loader.js';
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ export const CLAUDE_MODELS = {
|
|||||||
{ value: "haiku", label: "Haiku" },
|
{ value: "haiku", label: "Haiku" },
|
||||||
{ value: "opusplan", label: "Opus Plan" },
|
{ value: "opusplan", label: "Opus Plan" },
|
||||||
{ value: "sonnet[1m]", label: "Sonnet [1M]" },
|
{ value: "sonnet[1m]", label: "Sonnet [1M]" },
|
||||||
|
{ value: "opus[1m]", label: "Opus [1M]" },
|
||||||
],
|
],
|
||||||
|
|
||||||
DEFAULT: "sonnet",
|
DEFAULT: "opus",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,6 +59,7 @@ export const CURSOR_MODELS = {
|
|||||||
export const CODEX_MODELS = {
|
export const CODEX_MODELS = {
|
||||||
OPTIONS: [
|
OPTIONS: [
|
||||||
{ value: "gpt-5.4", label: "GPT-5.4" },
|
{ value: "gpt-5.4", label: "GPT-5.4" },
|
||||||
|
{ value: "gpt-5.4-mini", label: "GPT-5.4 mini" },
|
||||||
{ value: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
|
{ value: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
|
||||||
{ value: "gpt-5.2-codex", label: "GPT-5.2 Codex" },
|
{ value: "gpt-5.2-codex", label: "GPT-5.2 Codex" },
|
||||||
{ value: "gpt-5.2", label: "GPT-5.2" },
|
{ value: "gpt-5.2", label: "GPT-5.2" },
|
||||||
@@ -88,5 +90,5 @@ export const GEMINI_MODELS = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
DEFAULT: "gemini-2.5-flash",
|
DEFAULT: "gemini-3.1-pro-preview",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { authenticatedFetch } from "../../../utils/api";
|
import { authenticatedFetch } from "../../../utils/api";
|
||||||
import { ReleaseInfo } from "../../../types/sharedTypes";
|
import { ReleaseInfo } from "../../../types/sharedTypes";
|
||||||
@@ -15,6 +15,8 @@ interface VersionUpgradeModalProps {
|
|||||||
installMode: InstallMode;
|
installMode: InstallMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RELOAD_COUNTDOWN_START = 30;
|
||||||
|
|
||||||
export function VersionUpgradeModal({
|
export function VersionUpgradeModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -32,10 +34,30 @@ export function VersionUpgradeModal({
|
|||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
const [updateOutput, setUpdateOutput] = useState('');
|
const [updateOutput, setUpdateOutput] = useState('');
|
||||||
const [updateError, setUpdateError] = useState('');
|
const [updateError, setUpdateError] = useState('');
|
||||||
|
const [reloadCountdown, setReloadCountdown] = useState<number | null>(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 () => {
|
const handleUpdateNow = useCallback(async () => {
|
||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
setUpdateOutput('Starting update...\n');
|
setUpdateOutput('Starting update...\n');
|
||||||
|
setReloadCountdown(IS_PLATFORM ? RELOAD_COUNTDOWN_START : null);
|
||||||
setUpdateError('');
|
setUpdateError('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -49,8 +71,7 @@ export function VersionUpgradeModal({
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setUpdateOutput(prev => prev + data.output + '\n');
|
setUpdateOutput(prev => prev + data.output + '\n');
|
||||||
setUpdateOutput(prev => prev + '\n✅ Update completed successfully!\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 + 'Please restart the server to apply changes.' + '\n');
|
||||||
setUpdateOutput(prev => prev + text + '\n');
|
|
||||||
} else {
|
} else {
|
||||||
setUpdateError(data.error || 'Update failed');
|
setUpdateError(data.error || 'Update failed');
|
||||||
setUpdateOutput(prev => prev + '\n❌ Update failed: ' + (data.error || 'Unknown error') + '\n');
|
setUpdateOutput(prev => prev + '\n❌ Update failed: ' + (data.error || 'Unknown error') + '\n');
|
||||||
@@ -147,6 +168,13 @@ export function VersionUpgradeModal({
|
|||||||
<div className="max-h-48 overflow-y-auto rounded-lg border border-gray-700 bg-gray-900 p-4 dark:bg-gray-950">
|
<div className="max-h-48 overflow-y-auto rounded-lg border border-gray-700 bg-gray-900 p-4 dark:bg-gray-950">
|
||||||
<pre className="whitespace-pre-wrap font-mono text-xs text-green-400">{updateOutput}</pre>
|
<pre className="whitespace-pre-wrap font-mono text-xs text-green-400">{updateOutput}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
{IS_PLATFORM && reloadCountdown !== null && (
|
||||||
|
<div className="rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-700 dark:border-blue-900/40 dark:bg-blue-900/20 dark:text-blue-200">
|
||||||
|
{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.`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{updateError && (
|
{updateError && (
|
||||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900/40 dark:bg-red-900/20 dark:text-red-200">
|
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900/40 dark:bg-red-900/20 dark:text-red-200">
|
||||||
{updateError}
|
{updateError}
|
||||||
|
|||||||
Reference in New Issue
Block a user