mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 15:39:41 +02:00
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:
+57
-44
@@ -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;
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
/* Claudeman - Terminal-First UI */
|
||||
/* Codeman - Terminal-First UI */
|
||||
|
||||
:root {
|
||||
--bg-dark: #0a0a0a;
|
||||
|
||||
@@ -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="/">← Back to Claudeman</a>
|
||||
<a class="back" href="/">← Back to Codeman</a>
|
||||
<h1>Upload Screenshot</h1>
|
||||
<div class="drop-zone" id="drop">
|
||||
<p>Tap to select image</p>
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ const ALLOWED_ENV_PREFIXES = ['CLAUDE_CODE_', 'OPENCODE_'];
|
||||
/** Env var keys that are always blocked (security-sensitive) */
|
||||
const BLOCKED_ENV_KEYS = new Set([
|
||||
'PATH', 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'NODE_OPTIONS',
|
||||
'CLAUDEMAN_MUX_NAME', 'CLAUDEMAN_TMUX',
|
||||
'CODEMAN_MUX_NAME', 'CODEMAN_TMUX',
|
||||
'OPENCODE_SERVER_PASSWORD', // Security-sensitive: server auth password
|
||||
]);
|
||||
|
||||
|
||||
+35
-35
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @fileoverview Claudeman web server and REST API
|
||||
* @fileoverview Codeman web server and REST API
|
||||
*
|
||||
* Provides a Fastify-based web server with:
|
||||
* - REST API for session management, respawn control, and monitoring
|
||||
@@ -142,7 +142,7 @@ const MAX_HOOK_DATA_SIZE = 8 * 1024;
|
||||
// Maximum screenshot upload size (10MB)
|
||||
const MAX_SCREENSHOT_SIZE = 10 * 1024 * 1024;
|
||||
// Screenshots directory
|
||||
const SCREENSHOTS_DIR = join(homedir(), '.claudeman', 'screenshots');
|
||||
const SCREENSHOTS_DIR = join(homedir(), '.codeman', 'screenshots');
|
||||
// Stats collection interval (2 seconds)
|
||||
const STATS_COLLECTION_INTERVAL_MS = 2000;
|
||||
// Session limit wait time before retrying (5 seconds)
|
||||
@@ -272,10 +272,10 @@ function autoConfigureRalph(session: Session, workingDir: string, broadcast: (ev
|
||||
|
||||
/**
|
||||
* Get or generate a self-signed TLS certificate for HTTPS.
|
||||
* Certs are stored in ~/.claudeman/certs/ and reused across restarts.
|
||||
* Certs are stored in ~/.codeman/certs/ and reused across restarts.
|
||||
*/
|
||||
function getOrCreateSelfSignedCert(): { key: string; cert: string } {
|
||||
const certsDir = join(homedir(), '.claudeman', 'certs');
|
||||
const certsDir = join(homedir(), '.codeman', 'certs');
|
||||
const keyPath = join(certsDir, 'server.key');
|
||||
const certPath = join(certsDir, 'server.crt');
|
||||
|
||||
@@ -292,7 +292,7 @@ function getOrCreateSelfSignedCert(): { key: string; cert: string } {
|
||||
execSync(
|
||||
`openssl req -x509 -newkey rsa:2048 -nodes ` +
|
||||
`-keyout "${keyPath}" -out "${certPath}" ` +
|
||||
`-days 365 -subj "/CN=claudeman" ` +
|
||||
`-days 365 -subj "/CN=codeman" ` +
|
||||
`-addext "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:0.0.0.0"`,
|
||||
{ stdio: 'pipe' }
|
||||
);
|
||||
@@ -582,10 +582,10 @@ export class WebServer extends EventEmitter {
|
||||
threshold: 1024,
|
||||
});
|
||||
|
||||
// Optional HTTP Basic Auth (set CLAUDEMAN_PASSWORD env var to enable)
|
||||
const authPassword = process.env.CLAUDEMAN_PASSWORD;
|
||||
// Optional HTTP Basic Auth (set CODEMAN_PASSWORD env var to enable)
|
||||
const authPassword = process.env.CODEMAN_PASSWORD;
|
||||
if (authPassword) {
|
||||
const authUsername = process.env.CLAUDEMAN_USERNAME || 'admin';
|
||||
const authUsername = process.env.CODEMAN_USERNAME || 'admin';
|
||||
const expectedHeader = 'Basic ' + Buffer.from(`${authUsername}:${authPassword}`).toString('base64');
|
||||
this.app.addHook('onRequest', (req, reply, done) => {
|
||||
const auth = req.headers.authorization;
|
||||
@@ -593,7 +593,7 @@ export class WebServer extends EventEmitter {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
reply.header('WWW-Authenticate', 'Basic realm="Claudeman"');
|
||||
reply.header('WWW-Authenticate', 'Basic realm="Codeman"');
|
||||
reply.code(401).send('Unauthorized');
|
||||
});
|
||||
}
|
||||
@@ -2350,7 +2350,7 @@ export class WebServer extends EventEmitter {
|
||||
});
|
||||
|
||||
// Case management
|
||||
const casesDir = join(homedir(), 'claudeman-cases');
|
||||
const casesDir = join(homedir(), 'codeman-cases');
|
||||
|
||||
this.app.get('/api/cases', async (): Promise<CaseInfo[]> => {
|
||||
const cases: CaseInfo[] = [];
|
||||
@@ -2372,7 +2372,7 @@ export class WebServer extends EventEmitter {
|
||||
}
|
||||
|
||||
// Get linked cases
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
for (const [name, path] of Object.entries(linkedCases)) {
|
||||
@@ -2456,11 +2456,11 @@ export class WebServer extends EventEmitter {
|
||||
// Check if case name already exists in casesDir
|
||||
const casePath = join(casesDir, name);
|
||||
if (existsSync(casePath)) {
|
||||
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in claudeman-cases.');
|
||||
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in codeman-cases.');
|
||||
}
|
||||
|
||||
// Load existing linked cases
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
let linkedCases: Record<string, string> = {};
|
||||
try {
|
||||
linkedCases = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
@@ -2478,9 +2478,9 @@ export class WebServer extends EventEmitter {
|
||||
// Save the linked case
|
||||
linkedCases[name] = expandedPath;
|
||||
try {
|
||||
const claudemanDir = join(homedir(), '.claudeman');
|
||||
if (!existsSync(claudemanDir)) {
|
||||
mkdirSync(claudemanDir, { recursive: true });
|
||||
const codemanDir = join(homedir(), '.codeman');
|
||||
if (!existsSync(codemanDir)) {
|
||||
mkdirSync(codemanDir, { recursive: true });
|
||||
}
|
||||
await fs.writeFile(linkedCasesFile, JSON.stringify(linkedCases, null, 2));
|
||||
this.broadcast('case:linked', { name, path: expandedPath });
|
||||
@@ -2494,7 +2494,7 @@ export class WebServer extends EventEmitter {
|
||||
const { name } = req.params as { name: string };
|
||||
|
||||
// First check linked cases
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
if (linkedCases[name]) {
|
||||
@@ -2531,7 +2531,7 @@ export class WebServer extends EventEmitter {
|
||||
// Get case path (check linked cases first, then casesDir)
|
||||
let casePath: string | null = null;
|
||||
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
if (linkedCases[name]) {
|
||||
@@ -2737,7 +2737,7 @@ export class WebServer extends EventEmitter {
|
||||
|
||||
// Save lastUsedCase to settings for TUI/web sync
|
||||
try {
|
||||
const settingsFilePath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsFilePath = join(homedir(), '.codeman', 'settings.json');
|
||||
let settings: Record<string, unknown> = {};
|
||||
try {
|
||||
settings = JSON.parse(await fs.readFile(settingsFilePath, 'utf-8'));
|
||||
@@ -2974,7 +2974,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
// Determine output directory for saving wizard results
|
||||
let outputDir: string | undefined;
|
||||
if (caseName) {
|
||||
const casesDir = join(homedir(), 'claudeman-cases');
|
||||
const casesDir = join(homedir(), 'codeman-cases');
|
||||
const casePath = join(casesDir, caseName);
|
||||
// Security: Path traversal protection - use relative path check
|
||||
const resolvedCase = resolve(casePath);
|
||||
@@ -3101,7 +3101,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
// Get ralph-wizard files for a case (prompts and results)
|
||||
this.app.get('/api/cases/:caseName/ralph-wizard/files', async (req) => {
|
||||
const { caseName } = req.params as { caseName: string };
|
||||
const casesDir = join(homedir(), 'claudeman-cases');
|
||||
const casesDir = join(homedir(), 'codeman-cases');
|
||||
let casePath = join(casesDir, caseName);
|
||||
|
||||
// Security: Path traversal protection - use relative path check
|
||||
@@ -3114,7 +3114,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
|
||||
// Check linked cases if path doesn't exist
|
||||
if (!existsSync(casePath)) {
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
if (linkedCases[caseName]) {
|
||||
@@ -3162,7 +3162,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
// Cache disabled to ensure fresh prompts when starting new plan generations
|
||||
this.app.get('/api/cases/:caseName/ralph-wizard/file/:filePath', async (req, reply) => {
|
||||
const { caseName, filePath } = req.params as { caseName: string; filePath: string };
|
||||
const casesDir = join(homedir(), 'claudeman-cases');
|
||||
const casesDir = join(homedir(), 'codeman-cases');
|
||||
let casePath = join(casesDir, caseName);
|
||||
|
||||
// Prevent browser caching - prompts change between plan generations
|
||||
@@ -3180,7 +3180,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
|
||||
// Check linked cases if path doesn't exist
|
||||
if (!existsSync(casePath)) {
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
if (linkedCases[caseName]) {
|
||||
@@ -3369,7 +3369,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
});
|
||||
|
||||
// ============ App Settings Endpoints ============
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
|
||||
this.app.get('/api/settings', async () => {
|
||||
try {
|
||||
@@ -3520,7 +3520,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
|
||||
// ============ Subagent Window State Endpoints ============
|
||||
// Persists minimized/open window states for cross-browser sync
|
||||
const windowStatesPath = join(homedir(), '.claudeman', 'subagent-window-states.json');
|
||||
const windowStatesPath = join(homedir(), '.codeman', 'subagent-window-states.json');
|
||||
|
||||
this.app.get('/api/subagent-window-states', async () => {
|
||||
try {
|
||||
@@ -3555,7 +3555,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
// ============ Subagent Parent Associations ============
|
||||
// Persists which TAB each agent window connects to.
|
||||
// This is the PERMANENT record of agent -> tab associations.
|
||||
const parentMapPath = join(homedir(), '.claudeman', 'subagent-parents.json');
|
||||
const parentMapPath = join(homedir(), '.codeman', 'subagent-parents.json');
|
||||
|
||||
this.app.get('/api/subagent-parents', async () => {
|
||||
try {
|
||||
@@ -3836,7 +3836,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
ext = map[ctMatch[1].toLowerCase()] ?? ext;
|
||||
}
|
||||
|
||||
// Save to ~/.claudeman/screenshots/
|
||||
// Save to ~/.codeman/screenshots/
|
||||
if (!existsSync(SCREENSHOTS_DIR)) {
|
||||
mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
||||
}
|
||||
@@ -4707,7 +4707,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
|
||||
// Helper to get custom CLAUDE.md template path from settings
|
||||
private async getDefaultClaudeMdPath(): Promise<string | undefined> {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(settingsPath, 'utf-8');
|
||||
@@ -4723,7 +4723,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Read ~/.claudeman/settings.json once and return the parsed object.
|
||||
// Read ~/.codeman/settings.json once and return the parsed object.
|
||||
// Cached for 2s to avoid redundant reads during session creation bursts.
|
||||
private _settingsCache: { data: Record<string, unknown>; ts: number } | null = null;
|
||||
private async readSettings(): Promise<Record<string, unknown>> {
|
||||
@@ -4731,7 +4731,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
if (this._settingsCache && now - this._settingsCache.ts < 2000) {
|
||||
return this._settingsCache.data;
|
||||
}
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
try {
|
||||
const content = await fs.readFile(settingsPath, 'utf-8');
|
||||
const data = JSON.parse(content) as Record<string, unknown>;
|
||||
@@ -5293,10 +5293,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
|
||||
await this.app.listen({ port: this.port, host: '0.0.0.0' });
|
||||
const protocol = this.https ? 'https' : 'http';
|
||||
console.log(`Claudeman web interface running at ${protocol}://localhost:${this.port}`);
|
||||
console.log(`Codeman web interface running at ${protocol}://localhost:${this.port}`);
|
||||
|
||||
// Set API URL for child processes (MCP server, spawned sessions)
|
||||
process.env.CLAUDEMAN_API_URL = `${protocol}://localhost:${this.port}`;
|
||||
process.env.CODEMAN_API_URL = `${protocol}://localhost:${this.port}`;
|
||||
|
||||
// Start scheduled runs cleanup timer
|
||||
this.scheduledCleanupTimer = setInterval(() => {
|
||||
@@ -5339,7 +5339,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
* Check if subagent tracking is enabled in settings (default: true)
|
||||
*/
|
||||
private async isSubagentTrackingEnabled(): Promise<boolean> {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
try {
|
||||
const content = await fs.readFile(settingsPath, 'utf-8');
|
||||
const settings = JSON.parse(content);
|
||||
@@ -5357,7 +5357,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
* Check if image watcher is enabled in settings (default: false)
|
||||
*/
|
||||
private async isImageWatcherEnabled(): Promise<boolean> {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
try {
|
||||
const content = await fs.readFile(settingsPath, 'utf-8');
|
||||
const settings = JSON.parse(content);
|
||||
|
||||
Reference in New Issue
Block a user