From 87e48946166701ff8453d50aded924a84fc102fa Mon Sep 17 00:00:00 2001 From: Simon Skinner Date: Fri, 29 Aug 2025 14:06:03 +0000 Subject: [PATCH 1/4] chore(release): v2.6.0 --- CHANGELOG.md | 13 ++++++++ README.md | 24 ++++++++++++++ bin/cc-web.js | 8 ++++- package.json | 2 +- src/public/app.js | 60 +++++++++++++++++++++++++++++------ src/public/session-manager.js | 13 ++++++-- src/server.js | 8 ++++- test/server-alias.test.js | 22 +++++++++++++ 8 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 test/server-alias.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 49c62e1..a795933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,3 +100,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.1.3] - Previous Release - Previous version baseline +## [2.6.0] - 2025-08-29 + +### Added +- Assistant alias support across CLI, server, and UI. + - New CLI flags: `--claude-alias ` and `--codex-alias `. + - New env vars: `CLAUDE_ALIAS`, `CODEX_ALIAS`. + - `/api/config` now returns `aliases` for the frontend. +- UI now displays configured aliases in buttons, prompts, and messages. +- Tests: added `test/server-alias.test.js` to validate server alias configuration. + +### Changed +- Startup logs show configured aliases. +- README updated with alias usage examples. diff --git a/README.md b/README.md index de88fec..f70d3c7 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,30 @@ npx claude-code-web --https --cert /path/to/cert.pem --key /path/to/key.pem ```bash # Enable additional logging and debugging npx claude-code-web --dev + +### Assistant Aliases + +You can customize how the assistants are labeled in the UI (for example, to display "Alice" instead of "Claude" or "R2" instead of "Codex"). + +- Flags: + - `--claude-alias `: Set the display name for Claude (default: env `CLAUDE_ALIAS` or "Claude"). + - `--codex-alias `: Set the display name for Codex (default: env `CODEX_ALIAS` or "Codex"). + +Examples: + +``` +npx claude-code-web --claude-alias Alice --codex-alias R2 +``` + +Or via environment variables: + +``` +export CLAUDE_ALIAS=Alice +export CODEX_ALIAS=R2 +npx claude-code-web +``` + +These aliases are for display purposes only; they do not change which underlying CLI is launched. ``` ### Running from source diff --git a/bin/cc-web.js b/bin/cc-web.js index a0ac071..64afab5 100755 --- a/bin/cc-web.js +++ b/bin/cc-web.js @@ -11,7 +11,7 @@ const program = new Command(); program .name('cc-web') .description('Web-based interface for Claude Code CLI') - .version('2.0.0') + .version('2.6.0') .option('-p, --port ', 'port to run the server on', '32352') .option('--no-open', 'do not automatically open browser') .option('--auth ', 'authentication token for secure access') @@ -21,6 +21,8 @@ program .option('--key ', 'path to SSL private key file') .option('--dev', 'development mode with additional logging') .option('--plan ', 'subscription plan (pro, max5, max20)', 'max20') + .option('--claude-alias ', 'display alias for Claude (default: env CLAUDE_ALIAS or "Claude")') + .option('--codex-alias ', 'display alias for Codex (default: env CODEX_ALIAS or "Codex")') .option('--ngrok-auth-token ', 'ngrok auth token to open a public tunnel') .option('--ngrok-domain ', 'ngrok reserved domain to use for the tunnel') .parse(); @@ -68,6 +70,9 @@ async function main() { key: options.key, dev: options.dev, plan: options.plan, + // UI aliases for assistants + claudeAlias: options.claudeAlias || process.env.CLAUDE_ALIAS || 'Claude', + codexAlias: options.codexAlias || process.env.CODEX_ALIAS || 'Codex', folderMode: true // Always use folder mode }; @@ -75,6 +80,7 @@ async function main() { console.log(`Port: ${port}`); console.log('Mode: Folder selection mode'); console.log(`Plan: ${options.plan}`); + console.log(`Aliases: Claude → "${serverOptions.claudeAlias}", Codex → "${serverOptions.codexAlias}"`); // Display authentication status prominently if (noAuth) { diff --git a/package.json b/package.json index 7caea69..3d422a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-code-web", - "version": "2.5.3", + "version": "2.6.0", "description": "Web-based interface for Claude Code CLI accessible via browser", "main": "src/server.js", "bin": { diff --git a/src/public/app.js b/src/public/app.js index e2d69b5..a7ea914 100644 --- a/src/public/app.js +++ b/src/public/app.js @@ -18,6 +18,8 @@ class ClaudeCodeWebInterface { this.currentMode = 'chat'; this.planDetector = null; this.planModal = null; + // Aliases for assistants (populated from /api/config) + this.aliases = { claude: 'Claude', codex: 'Codex' }; // Initialize the session tab manager @@ -66,10 +68,12 @@ class ClaudeCodeWebInterface { return; } + await this.loadConfig(); this.setupTerminal(); this.setupUI(); this.setupPlanDetector(); this.loadSettings(); + this.applyAliasesToUI(); this.disablePullToRefresh(); // Show loading while we initialize @@ -111,6 +115,44 @@ class ClaudeCodeWebInterface { this.disconnect(); }); } + + async loadConfig() { + try { + const res = await this.authFetch('/api/config'); + if (res.ok) { + const cfg = await res.json(); + if (cfg?.aliases) { + this.aliases = { + claude: cfg.aliases.claude || 'Claude', + codex: cfg.aliases.codex || 'Codex' + }; + } + if (typeof cfg.folderMode === 'boolean') { + this.folderMode = cfg.folderMode; + } + } + } catch (_) { /* best-effort */ } + } + + getAlias(kind) { + return (this.aliases && this.aliases[kind]) ? this.aliases[kind] : (kind === 'codex' ? 'Codex' : 'Claude'); + } + + applyAliasesToUI() { + // Start prompt buttons + const startBtn = document.getElementById('startBtn'); + const dangerousSkipBtn = document.getElementById('dangerousSkipBtn'); + const startCodexBtn = document.getElementById('startCodexBtn'); + const dangerousCodexBtn = document.getElementById('dangerousCodexBtn'); + if (startBtn) startBtn.textContent = `Start ${this.getAlias('claude')}`; + if (dangerousSkipBtn) dangerousSkipBtn.textContent = `Dangerous ${this.getAlias('claude')}`; + if (startCodexBtn) startCodexBtn.textContent = `Start ${this.getAlias('codex')}`; + if (dangerousCodexBtn) dangerousCodexBtn.textContent = `Dangerous ${this.getAlias('codex')}`; + + // Plan modal title + const planTitle = document.querySelector('#planModal .modal-header h2'); + if (planTitle) planTitle.textContent = `📋 ${this.getAlias('claude')}'s Plan`; + } detectMobile() { // Check for touch capability and common mobile user agents @@ -528,7 +570,7 @@ class ClaudeCodeWebInterface { async runCommandFromPath(relPath) { if (!this.currentClaudeSessionId) { - this.showError('Start Claude/Codex in a session first'); + this.showError(`Start ${this.getAlias('claude')}/${this.getAlias('codex')} in a session first`); return; } try { @@ -574,7 +616,7 @@ class ClaudeCodeWebInterface { return; } if (!this.currentClaudeSessionId) { - this.showError('Start Claude/Codex in a session first'); + this.showError(`Start ${this.getAlias('claude')}/${this.getAlias('codex')} in a session first`); return; } // Send and close @@ -782,7 +824,7 @@ class ClaudeCodeWebInterface { console.log('[session_joined] Existing session with stopped Claude, showing restart prompt'); // For existing sessions where Claude has stopped, show start prompt // This allows the user to restart Claude in the same session - this.terminal.writeln('\r\n\x1b[33mClaude Code has stopped in this session. Click "Start Claude Code" to restart.\x1b[0m'); + this.terminal.writeln(`\r\n\x1b[33m${this.getAlias('claude')} has stopped in this session. Click "Start ${this.getAlias('claude')}" to restart.\x1b[0m`); this.showOverlay('startPrompt'); } } @@ -829,7 +871,7 @@ class ClaudeCodeWebInterface { break; case 'claude_stopped': - this.terminal.writeln(`\r\n\x1b[33mClaude Code stopped\x1b[0m`); + this.terminal.writeln(`\r\n\x1b[33m${this.getAlias('claude')} stopped\x1b[0m`); // Show start prompt to allow restarting Claude in this session this.showOverlay('startPrompt'); this.loadSessions(); // Refresh session list @@ -857,7 +899,7 @@ class ClaudeCodeWebInterface { break; case 'exit': - this.terminal.writeln(`\r\n\x1b[33mClaude Code exited with code ${message.code}\x1b[0m`); + this.terminal.writeln(`\r\n\x1b[33m${this.getAlias('claude')} exited with code ${message.code}\x1b[0m`); // Mark session as error if non-zero exit code if (this.sessionTabManager && this.currentClaudeSessionId && message.code !== 0) { @@ -931,8 +973,8 @@ class ClaudeCodeWebInterface { this.showOverlay('loadingSpinner'); const loadingText = options.dangerouslySkipPermissions ? - 'Starting Claude Code (⚠️ Skipping permissions)...' : - 'Starting Claude Code...'; + `Starting ${this.getAlias('claude')} (⚠️ Skipping permissions)...` : + `Starting ${this.getAlias('claude')}...`; document.getElementById('loadingSpinner').querySelector('p').textContent = loadingText; } @@ -955,8 +997,8 @@ class ClaudeCodeWebInterface { this.showOverlay('loadingSpinner'); const loadingText = options.dangerouslySkipPermissions ? - 'Starting Codex (⚠️ Bypassing approvals and sandbox)...' : - 'Starting Codex...'; + `Starting ${this.getAlias('codex')} (⚠️ Bypassing approvals and sandbox)...` : + `Starting ${this.getAlias('codex')}...`; document.getElementById('loadingSpinner').querySelector('p').textContent = loadingText; } diff --git a/src/public/session-manager.js b/src/public/session-manager.js index d146f34..f3f6d6e 100644 --- a/src/public/session-manager.js +++ b/src/public/session-manager.js @@ -7,6 +7,13 @@ class SessionTabManager { this.notificationsEnabled = false; this.requestNotificationPermission(); } + + getAlias(kind) { + if (this.claudeInterface && typeof this.claudeInterface.getAlias === 'function') { + return this.claudeInterface.getAlias(kind); + } + return kind === 'codex' ? 'Codex' : 'Claude'; + } requestNotificationPermission() { if ('Notification' in window) { @@ -216,7 +223,7 @@ class SessionTabManager { promptDiv.innerHTML = `
Enable Desktop Notifications?
- Get notified when Claude completes tasks in background tabs. + Get notified when ${this.getAlias('claude')} completes tasks in background tabs.
+
+ + +
From 90a085fbd35761cfbe4c8dd534c241e4a191c684 Mon Sep 17 00:00:00 2001 From: Simon Skinner Date: Sat, 13 Sep 2025 20:50:56 +0000 Subject: [PATCH 4/4] feat(ui): tiled view (2-pane) with resizable splitter and per-pane sessions\n\nchore(release): v2.10.0 --- CHANGELOG.md | 12 ++ package-lock.json | 4 +- package.json | 2 +- src/public/app.js | 21 ++++ src/public/index.html | 42 ++++++- src/public/panes.js | 216 ++++++++++++++++++++++++++++++++++ src/public/session-manager.js | 5 + src/public/style.css | 28 +++++ 8 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 src/public/panes.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 64537af..d1ad9a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,3 +144,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Notes - UI-only change; no server/CLI APIs modified. +## [2.10.0] - 2025-09-13 + +### Added +- Tiled View (MVP): view two sessions side‑by‑side with independent terminals and sockets. +- Resizable splitter between panes with persistent split position. +- Per‑pane session picker and close controls; layout and assignments persist in localStorage. + +### Changed +- Settings font size now applies to all visible panes in tiled view. + +### Notes +- Client‑side only; no server/CLI changes required. Default remains single‑pane; toggle via new tile button in the top bar. diff --git a/package-lock.json b/package-lock.json index 1416143..79bfe83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-web", - "version": "2.9.0", + "version": "2.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-web", - "version": "2.9.0", + "version": "2.10.0", "license": "MIT", "dependencies": { "@ngrok/ngrok": "^1.4.0", diff --git a/package.json b/package.json index dbd5592..d0d2b6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-code-web", - "version": "2.9.0", + "version": "2.10.0", "description": "Web-based interface for Claude Code CLI accessible via browser", "main": "src/server.js", "bin": { diff --git a/src/public/app.js b/src/public/app.js index 69bc850..f6aff91 100644 --- a/src/public/app.js +++ b/src/public/app.js @@ -32,6 +32,7 @@ class ClaudeCodeWebInterface { this.sessionTimer = null; this.sessionTimerInterval = null; + this.paneManager = null; this.init(); } @@ -72,6 +73,8 @@ class ClaudeCodeWebInterface { this.setupTerminal(); this.setupUI(); this.setupPlanDetector(); + // Pane manager after UI exists + this.paneManager = new PaneManager(this); this.loadSettings(); this.applyAliasesToUI(); this.disablePullToRefresh(); @@ -109,6 +112,7 @@ class ClaudeCodeWebInterface { window.addEventListener('resize', () => { this.fitTerminal(); + if (this.paneManager?.enabled) this.paneManager.panes.forEach(p => p.fit()); }); window.addEventListener('beforeunload', () => { @@ -432,6 +436,19 @@ class ClaudeCodeWebInterface { if (settingsBtn) settingsBtn.addEventListener('click', () => this.showSettings()); if (retryBtn) retryBtn.addEventListener('click', () => this.reconnect()); + // Tile view toggle + const tileToggle = document.getElementById('tileViewToggle'); + if (tileToggle) { + tileToggle.addEventListener('click', () => { + if (!this.paneManager) return; + if (this.paneManager.enabled) { + this.paneManager.disable(); + } else { + this.paneManager.enable(); + } + }); + } + // Mobile menu event listeners if (closeMenuBtn) closeMenuBtn.addEventListener('click', () => this.closeMobileMenu()); if (settingsBtnMobile) { @@ -947,6 +964,8 @@ class ClaudeCodeWebInterface { message.plan, message.limits ); + // Also refresh pane session selectors when sessions list changes + if (this.paneManager) this.paneManager.refreshSessionSelects(); break; default: @@ -1155,6 +1174,7 @@ class ClaudeCodeWebInterface { } this.terminal.options.fontSize = settings.fontSize; + if (this.paneManager?.panes) this.paneManager.panes.forEach(p => { if (p.terminal) p.terminal.options.fontSize = settings.fontSize; p.fit();}); this.fitTerminal(); } @@ -2248,5 +2268,6 @@ document.head.appendChild(style); document.addEventListener('DOMContentLoaded', () => { const app = new ClaudeCodeWebInterface(); + window.app = app; app.startHeartbeat(); }); diff --git a/src/public/index.html b/src/public/index.html index f1b3fa2..b7b804c 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -106,12 +106,21 @@
- + + + @@ -130,7 +139,7 @@