diff --git a/CHANGELOG.md b/CHANGELOG.md index 49c62e1..6b31460 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.1] - 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..168f712 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.1') .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..2f5315b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-code-web", - "version": "2.5.3", + "version": "2.6.1", "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.