chore: rename Claudeman to Codeman

Full product rename across 109 files (~834 occurrences):
- Env vars: CLAUDEMAN_* → CODEMAN_*
- Data dirs: ~/.claudeman/ → ~/.codeman/, ~/claudeman-cases/ → ~/codeman-cases/
- tmux prefix: claudeman- → codeman-
- localStorage: claudeman-* → codeman-*
- Package/CLI: claudeman → codeman
- GitHub repo: Ark0N/Claudeman → Ark0N/Codeman
- systemd service: claudeman-web → codeman-web
- Class: ClaudemanApp → CodemanApp

Migration infrastructure for seamless transition:
- state-store.ts: auto-migrates data directories on startup
- tmux-manager.ts: dual-prefix detection (legacy claudeman- sessions)
- app.js: localStorage key migration (preserves old keys)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-02-26 16:44:34 +01:00
co-authored by Claude Opus 4.6
parent 3ccd22161c
commit 3795c45cc1
109 changed files with 711 additions and 679 deletions
+57 -44
View File
@@ -1,4 +1,4 @@
// Claudeman App - Tab-based Terminal UI
// Codeman App - Tab-based Terminal UI
// ============================================================================
// Constants
@@ -852,14 +852,14 @@ const VoiceInput = {
_getDeepgramConfig() {
try {
return JSON.parse(localStorage.getItem('claudeman-voice-settings') || '{}');
return JSON.parse(localStorage.getItem('codeman-voice-settings') || '{}');
} catch (_e) {
return {};
}
},
_saveDeepgramConfig(config) {
localStorage.setItem('claudeman-voice-settings', JSON.stringify(config));
localStorage.setItem('codeman-voice-settings', JSON.stringify(config));
},
_shouldUseDeepgram() {
@@ -2294,7 +2294,7 @@ class NotificationManager {
// Get storage key for notification prefs (device-specific)
getStorageKey() {
const isMobile = MobileDetection.getDeviceType() === 'mobile';
return isMobile ? 'claudeman-notification-prefs-mobile' : 'claudeman-notification-prefs';
return isMobile ? 'codeman-notification-prefs-mobile' : 'codeman-notification-prefs';
}
savePreferences() {
@@ -2449,11 +2449,11 @@ class NotificationManager {
this.titleFlashInterval = setInterval(() => {
this.titleFlashState = !this.titleFlashState;
document.title = this.titleFlashState
? `\u26A0\uFE0F (${this.unreadCount}) Claudeman`
? `\u26A0\uFE0F (${this.unreadCount}) Codeman`
: this.originalTitle;
}, TITLE_FLASH_INTERVAL_MS);
// Set immediately
document.title = `\u26A0\uFE0F (${this.unreadCount}) Claudeman`;
document.title = `\u26A0\uFE0F (${this.unreadCount}) Codeman`;
}
}
}
@@ -2488,7 +2488,7 @@ class NotificationManager {
if (now - this.lastBrowserNotifTime < 3000) return;
this.lastBrowserNotifTime = now;
const notif = new Notification(`Claudeman: ${title}`, {
const notif = new Notification(`Codeman: ${title}`, {
body,
tag, // Groups same-tag notifications
icon: '/favicon.ico',
@@ -2634,7 +2634,7 @@ class NotificationManager {
}
}
class ClaudemanApp {
class CodemanApp {
constructor() {
this.sessions = new Map();
this.sessionOrder = []; // Track tab order for drag-and-drop reordering
@@ -2919,7 +2919,7 @@ class ClaudemanApp {
initTerminal() {
// Load scrollback setting from localStorage (default 5000)
const scrollback = parseInt(localStorage.getItem('claudeman-scrollback')) || DEFAULT_SCROLLBACK;
const scrollback = parseInt(localStorage.getItem('codeman-scrollback')) || DEFAULT_SCROLLBACK;
this.terminal = new Terminal({
theme: {
@@ -4023,7 +4023,7 @@ class ClaudemanApp {
this._cleanupSessionData(data.id);
if (this.activeSessionId === data.id) {
this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {}
try { localStorage.removeItem('codeman-active-session'); } catch {}
this.terminal.clear();
this.showWelcome();
}
@@ -4709,11 +4709,11 @@ class ClaudemanApp {
}
this.renderSubagentPanel();
// Find which Claudeman session owns this subagent (direct claudeSessionId match only)
// Find which Codeman session owns this subagent (direct claudeSessionId match only)
this.findParentSessionForSubagent(data.agentId);
// Auto-open window for new active agents — but ONLY if they belong to a Claudeman session tab.
// Agents from external Claude sessions (not managed by Claudeman) should not pop up.
// Auto-open window for new active agents — but ONLY if they belong to a Codeman session tab.
// Agents from external Claude sessions (not managed by Codeman) should not pop up.
if (data.status === 'active') {
const agentForCheck = this.subagents.get(data.agentId);
const hasMatchingTab = agentForCheck?.sessionId &&
@@ -5106,11 +5106,11 @@ class ClaudemanApp {
const headerVersionEl = this.$('headerVersion');
if (versionEl) {
versionEl.textContent = `v${data.version}`;
versionEl.title = `Claudeman v${data.version}`;
versionEl.title = `Codeman v${data.version}`;
}
if (headerVersionEl) {
headerVersionEl.textContent = `v${data.version}`;
headerVersionEl.title = `Claudeman v${data.version}`;
headerVersionEl.title = `Codeman v${data.version}`;
}
}
@@ -5299,7 +5299,7 @@ class ClaudemanApp {
// Priority: current active > localStorage > first session
let restoreId = previousActiveId;
if (!restoreId || !this.sessions.has(restoreId)) {
try { restoreId = localStorage.getItem('claudeman-active-session'); } catch {}
try { restoreId = localStorage.getItem('codeman-active-session'); } catch {}
}
if (restoreId && this.sessions.has(restoreId)) {
this.selectSession(restoreId);
@@ -5605,7 +5605,7 @@ class ClaudemanApp {
// Load session order from localStorage
loadSessionOrder() {
try {
const saved = localStorage.getItem('claudeman-session-order');
const saved = localStorage.getItem('codeman-session-order');
return saved ? JSON.parse(saved) : [];
} catch {
return [];
@@ -5615,7 +5615,7 @@ class ClaudemanApp {
// Save session order to localStorage
saveSessionOrder() {
try {
localStorage.setItem('claudeman-session-order', JSON.stringify(this.sessionOrder));
localStorage.setItem('codeman-session-order', JSON.stringify(this.sessionOrder));
} catch {
// Ignore storage errors
}
@@ -5924,7 +5924,7 @@ class ClaudemanApp {
this._localEchoOverlay.suppressBufferDetection();
}
this.activeSessionId = sessionId;
try { localStorage.setItem('claudeman-active-session', sessionId); } catch {}
try { localStorage.setItem('codeman-active-session', sessionId); } catch {}
this.hideWelcome();
// Clear idle hooks on view, but keep action hooks until user interacts
this.clearPendingHooks(sessionId, 'idle_prompt');
@@ -6160,7 +6160,7 @@ class ClaudemanApp {
if (this.activeSessionId === sessionId) {
this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {}
try { localStorage.removeItem('codeman-active-session'); } catch {}
// Select another session or show welcome (use sessionOrder for consistent ordering)
if (this.sessionOrder.length > 0 && this.sessions.size > 0) {
const nextSessionId = this.sessionOrder[0];
@@ -6242,7 +6242,7 @@ class ClaudemanApp {
goHome() {
// Deselect active session and show welcome screen
this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {}
try { localStorage.removeItem('codeman-active-session'); } catch {}
this.terminal.clear();
this.showWelcome();
this.renderSessionTabs();
@@ -9250,7 +9250,7 @@ class ClaudemanApp {
} else {
// No cases exist yet - show the default case name as directory
select.value = 'testcase';
document.getElementById('dirDisplay').textContent = '~/claudeman-cases/testcase';
document.getElementById('dirDisplay').textContent = '~/codeman-cases/testcase';
this.updateMobileCaseLabel('testcase');
}
@@ -9317,7 +9317,7 @@ class ClaudemanApp {
setRunMode(mode) {
this._runMode = mode;
try { localStorage.setItem('claudeman_runMode', mode); } catch {}
try { localStorage.setItem('codeman_runMode', mode); } catch {}
this._applyRunMode();
// Close menu
document.getElementById('runModeMenu')?.classList.remove('active');
@@ -9361,7 +9361,7 @@ class ClaudemanApp {
}
_initRunMode() {
try { this._runMode = localStorage.getItem('claudeman_runMode') || 'claude'; } catch { this._runMode = 'claude'; }
try { this._runMode = localStorage.getItem('codeman_runMode') || 'claude'; } catch { this._runMode = 'claude'; }
this._applyRunMode();
}
@@ -10132,7 +10132,7 @@ class ClaudemanApp {
this.terminalBuffers.clear();
this.terminalBufferCache.clear();
this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {}
try { localStorage.removeItem('codeman-active-session'); } catch {}
this.respawnStatus = {};
this.respawnCountdownTimers = {};
this.respawnActionLogs = {};
@@ -10239,13 +10239,13 @@ class ClaudemanApp {
this.terminal.options.fontSize = size;
document.getElementById('fontSizeDisplay').textContent = size;
this.fitAddon.fit();
localStorage.setItem('claudeman-font-size', size);
localStorage.setItem('codeman-font-size', size);
// Update overlay font cache and re-render at new cell dimensions
this._localEchoOverlay?.refreshFont();
}
loadFontSize() {
const saved = localStorage.getItem('claudeman-font-size');
const saved = localStorage.getItem('codeman-font-size');
if (saved) {
const size = parseInt(saved, 10);
if (size >= 10 && size <= 24) {
@@ -10676,7 +10676,7 @@ class ClaudemanApp {
// ========== Respawn Presets ==========
loadRespawnPresets() {
const saved = localStorage.getItem('claudeman-respawn-presets');
const saved = localStorage.getItem('codeman-respawn-presets');
const custom = saved ? JSON.parse(saved) : [];
return [...BUILTIN_RESPAWN_PRESETS, ...custom];
}
@@ -10684,7 +10684,7 @@ class ClaudemanApp {
saveRespawnPresets(presets) {
// Only save custom presets (not built-in)
const custom = presets.filter(p => !p.builtIn);
localStorage.setItem('claudeman-respawn-presets', JSON.stringify(custom));
localStorage.setItem('codeman-respawn-presets', JSON.stringify(custom));
}
renderPresetDropdown() {
@@ -11842,7 +11842,7 @@ class ClaudemanApp {
// Get the settings storage key based on device type (mobile vs desktop)
getSettingsStorageKey() {
const isMobile = MobileDetection.getDeviceType() === 'mobile';
return isMobile ? 'claudeman-app-settings-mobile' : 'claudeman-app-settings';
return isMobile ? 'codeman-app-settings-mobile' : 'codeman-app-settings';
}
// Get default settings based on device type
@@ -12138,7 +12138,7 @@ class ClaudemanApp {
// Sync voice settings from server (seed localStorage if no local API key)
if (voiceSettings) {
const localVoice = localStorage.getItem('claudeman-voice-settings');
const localVoice = localStorage.getItem('codeman-voice-settings');
if (!localVoice || !JSON.parse(localVoice).apiKey) {
VoiceInput._saveDeepgramConfig(voiceSettings);
}
@@ -12179,7 +12179,7 @@ class ClaudemanApp {
const windowStates = { minimized: minimizedState, open: openWindows };
// Save to localStorage for quick restore
localStorage.setItem('claudeman-subagent-window-states', JSON.stringify(windowStates));
localStorage.setItem('codeman-subagent-window-states', JSON.stringify(windowStates));
// Save to server for cross-browser persistence
try {
@@ -12206,7 +12206,7 @@ class ClaudemanApp {
if (res.ok) {
states = await res.json();
// Also update localStorage
localStorage.setItem('claudeman-subagent-window-states', JSON.stringify(states));
localStorage.setItem('codeman-subagent-window-states', JSON.stringify(states));
}
} catch (err) {
console.error('Failed to load subagent window states from server:', err);
@@ -12215,7 +12215,7 @@ class ClaudemanApp {
// Fallback to localStorage
if (!states) {
try {
const saved = localStorage.getItem('claudeman-subagent-window-states');
const saved = localStorage.getItem('codeman-subagent-window-states');
if (saved) {
states = JSON.parse(saved);
}
@@ -12321,7 +12321,7 @@ class ClaudemanApp {
// Save to localStorage for instant recovery
try {
localStorage.setItem('claudeman-subagent-parents', JSON.stringify(mapData));
localStorage.setItem('codeman-subagent-parents', JSON.stringify(mapData));
} catch (err) {
console.error('Failed to save subagent parents to localStorage:', err);
}
@@ -12351,7 +12351,7 @@ class ClaudemanApp {
if (res.ok) {
mapData = await res.json();
// Update localStorage as cache
localStorage.setItem('claudeman-subagent-parents', JSON.stringify(mapData));
localStorage.setItem('codeman-subagent-parents', JSON.stringify(mapData));
}
} catch (err) {
console.error('Failed to load subagent parents from server:', err);
@@ -12360,7 +12360,7 @@ class ClaudemanApp {
// Fallback to localStorage
if (!mapData) {
try {
const saved = localStorage.getItem('claudeman-subagent-parents');
const saved = localStorage.getItem('codeman-subagent-parents');
if (saved) {
mapData = JSON.parse(saved);
}
@@ -14670,8 +14670,8 @@ class ClaudemanApp {
const agent = this.subagents.get(agentId);
if (!agent) return;
// Only open windows for agents that belong to a Claudeman-managed session tab.
// Agents from external Claude sessions (not tracked by Claudeman) should not pop up.
// Only open windows for agents that belong to a Codeman-managed session tab.
// Agents from external Claude sessions (not tracked by Codeman) should not pop up.
if (agent.sessionId) {
const hasMatchingTab = Array.from(this.sessions.values()).some(s => s.claudeSessionId === agent.sessionId);
if (!hasMatchingTab) return;
@@ -15610,7 +15610,7 @@ class ClaudemanApp {
/** Open a standalone terminal window for a tmux-pane teammate (no subagent entry needed) */
openTeammateTerminalWindow(paneData) {
// Only open if the session has a tab in Claudeman
// Only open if the session has a tab in Codeman
if (!this.sessions.has(paneData.sessionId)) return;
// Use pane target as the unique ID for this window
@@ -16831,7 +16831,7 @@ class ClaudemanApp {
this.sessions.clear();
this.muxSessions = [];
this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {}
try { localStorage.removeItem('codeman-active-session'); } catch {}
this.renderSessionTabs();
this.renderMuxSessions();
this.terminal.clear();
@@ -16842,7 +16842,7 @@ class ClaudemanApp {
// Just remove tabs, keep mux sessions running
this.sessions.clear();
this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {}
try { localStorage.removeItem('codeman-active-session'); } catch {}
this.renderSessionTabs();
this.terminal.clear();
this.terminal.reset();
@@ -17554,12 +17554,25 @@ class ClaudemanApp {
escapeHtml(text) {
if (!text) return '';
return text.replace(ClaudemanApp._htmlEscapePattern, char => ClaudemanApp._htmlEscapeMap[char]);
return text.replace(CodemanApp._htmlEscapePattern, char => CodemanApp._htmlEscapeMap[char]);
}
}
// Migrate legacy localStorage keys (claudeman-* → codeman-*)
try {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && (key.startsWith('claudeman-') || key.startsWith('claudeman_'))) {
const newKey = key.replace(/^claudeman[-_]/, (m) => 'codeman' + m.charAt(m.length - 1));
if (localStorage.getItem(newKey) === null) {
localStorage.setItem(newKey, localStorage.getItem(key));
}
}
}
} catch {}
// Initialize
const app = new ClaudemanApp();
const app = new CodemanApp();
// Expose for debugging/testing
window.app = app;
+9 -9
View File
@@ -6,7 +6,7 @@
<meta name="description" content="Claude Code session manager with web interface">
<meta name="theme-color" content="#0a0a0a">
<meta name="google" content="notranslate">
<title>Claudeman</title>
<title>Codeman</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
<link rel="stylesheet" href="styles.css?v=0.1631">
<link rel="stylesheet" href="mobile.css?v=0.1631" media="(max-width: 1023px)">
@@ -37,7 +37,7 @@
<!-- Loading skeleton — visible instantly before CSS/JS loads, hidden by app-loaded class -->
<div class="loading-skeleton">
<div class="skeleton-header">
<span class="skeleton-brand">Claudeman</span>
<span class="skeleton-brand">Codeman</span>
<div class="skeleton-tabs"><div class="skeleton-tab"></div></div>
</div>
<div class="skeleton-terminal"></div>
@@ -49,7 +49,7 @@
<!-- Compact Header with Session Tabs -->
<header class="header">
<div class="header-brand">
<span class="logo" onclick="app.goHome()" title="Go to main page">Claudeman</span>
<span class="logo" onclick="app.goHome()" title="Go to main page">Codeman</span>
</div>
<!-- Session Tabs -->
@@ -231,7 +231,7 @@
<!-- Welcome Overlay (shown when no session active) -->
<div class="welcome-overlay" id="welcomeOverlay">
<div class="welcome-content">
<h1 class="welcome-title">Claudeman</h1>
<h1 class="welcome-title">Codeman</h1>
<p class="welcome-desc">Manage AI Coding tools in persistent tmux sessions.</p>
<div class="welcome-actions">
<button class="welcome-btn welcome-btn-claude" onclick="app.setRunMode('claude'); app.runClaude()">
@@ -379,7 +379,7 @@
</div>
<div class="toolbar-center">
<span class="version-display" id="versionDisplay" title="Claudeman version">v0.0.0</span>
<span class="version-display" id="versionDisplay" title="Codeman version">v0.0.0</span>
</div>
<div class="toolbar-right">
@@ -1086,7 +1086,7 @@
<span class="settings-status" id="notifPermissionStatus">?</span>
</div>
</div>
<span class="form-hint" style="grid-column: 1 / -1; margin-top: -8px;">For remote access, HTTPS is required. Start with: <code>claudeman web --https</code></span>
<span class="form-hint" style="grid-column: 1 / -1; margin-top: -8px;">For remote access, HTTPS is required. Start with: <code>codeman web --https</code></span>
<!-- Alerts Section -->
<div class="settings-section-header">Alerts</div>
@@ -1222,7 +1222,7 @@
<div class="form-row" style="grid-column: 1 / -1;">
<label for="voiceKeyterms">Domain Keywords</label>
<input type="text" id="voiceKeyterms" placeholder="claudeman, tmux, respawn, subagent" style="width: 100%; padding: 0.5rem 0.75rem; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); font-size: 0.8rem; font-family: inherit;">
<input type="text" id="voiceKeyterms" placeholder="codeman, tmux, respawn, subagent" style="width: 100%; padding: 0.5rem 0.75rem; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); font-size: 0.8rem; font-family: inherit;">
<span class="form-hint">Comma-separated terms to boost recognition accuracy</span>
</div>
</div>
@@ -1253,7 +1253,7 @@
<div class="form-row">
<label>Case Name</label>
<input type="text" id="newCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Letters, numbers, hyphens, underscores only. Created in ~/claudeman-cases/</span>
<span class="form-hint">Letters, numbers, hyphens, underscores only. Created in ~/codeman-cases/</span>
</div>
<div class="form-row">
<label>Description (optional)</label>
@@ -1265,7 +1265,7 @@
<div class="form-row">
<label>Case Name</label>
<input type="text" id="linkCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Name to identify this case in Claudeman</span>
<span class="form-hint">Name to identify this case in Codeman</span>
</div>
<div class="form-row">
<label>Folder Path</label>
+1 -1
View File
@@ -1,4 +1,4 @@
/* Claudeman - Terminal-First UI */
/* Codeman - Terminal-First UI */
:root {
--bg-dark: #0a0a0a;
+2 -2
View File
@@ -2,7 +2,7 @@
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Upload Screenshot - Claudeman</title>
<title>Upload Screenshot - Codeman</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
@@ -49,7 +49,7 @@
</style>
</head><body>
<div class="container">
<a class="back" href="/">&larr; Back to Claudeman</a>
<a class="back" href="/">&larr; Back to Codeman</a>
<h1>Upload Screenshot</h1>
<div class="drop-zone" id="drop">
<p>Tap to select image</p>