2026-01-18 05:14:34 +01:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
2026-06-06 03:53:54 +02:00
<!-- Resolve all relative assets against the site root so the same shell can be
served at /session/:id (detached single-session window) without 404ing
on relative <script>/<link> URLs. Must precede the first resource tag. -->
< base href = "/" >
2026-03-08 23:54:15 +00:00
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" >
2026-01-18 22:04:02 +01:00
< meta name = "description" content = "Claude Code session manager with web interface" >
2026-06-13 19:46:50 +02:00
< meta name = "theme-color" content = "#11151c" >
2026-02-18 11:59:42 +01:00
< meta name = "google" content = "notranslate" >
2026-04-03 22:40:05 -04:00
< meta name = "apple-mobile-web-app-capable" content = "yes" >
< meta name = "apple-mobile-web-app-status-bar-style" content = "black-translucent" >
< link rel = "apple-touch-icon" href = "icon-192.png" >
2026-02-27 08:28:30 +01:00
< link rel = "manifest" href = "manifest.json" >
2026-02-26 16:44:34 +01:00
< title > Codeman</ title >
2026-01-18 22:04:02 +01:00
< 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" >
2026-03-11 19:13:49 +01:00
< link rel = "stylesheet" href = "styles.css" >
< link rel = "stylesheet" href = "mobile.css" media = "(max-width: 1023px)" >
2026-02-18 11:59:42 +01:00
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
< link rel = "preload" href = "vendor/xterm.css" as = "style" onload = "this.onload=null;this.rel='stylesheet'" >
< noscript >< link rel = "stylesheet" href = "vendor/xterm.css" ></ noscript >
2026-03-14 19:20:44 +01:00
<!-- Preload critical resources — lets browser discover these during HTML parse
instead of waiting until <script> tags at bottom-of-body are reached. -->
< link rel = "preload" href = "vendor/xterm.min.js" as = "script" >
< link rel = "preload" href = "constants.js" as = "script" >
< link rel = "preload" href = "app.js" as = "script" >
2026-02-18 11:59:42 +01:00
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
2026-02-13 21:21:33 +01:00
'defer' preserves execution order (xterm loads before fit addon). -->
2026-02-18 11:59:42 +01:00
< script defer src = "vendor/xterm.min.js" ></ script >
< script defer src = "vendor/xterm-addon-fit.min.js" ></ script >
2026-06-10 19:56:20 -04:00
<!-- SerializeAddon: snapshots xterm state (viewport + scrollback + attrs) for
per-session restore on tab switches. Lets codex tabs survive switch-away
without codeman having to replay codex's byte stream (which loses earlier
conversation because codex's TUI redraws drop it from the viewport). -->
< script defer src = "vendor/xterm-addon-serialize.min.js" ></ script >
2026-03-14 19:20:44 +01:00
<!-- WebGL addon lazy-loaded by app.js on desktop only (skipped on mobile, saving 244KB) -->
2026-02-24 16:13:09 +01:00
< script defer src = "vendor/xterm-addon-unicode11.min.js" ></ script >
2026-03-11 19:13:49 +01:00
< script defer src = "vendor/xterm-zerolag-input.js" ></ script >
2026-08-09 04:26:39 +02:00
< script defer src = "vendor/xterm-predictive-echo.js" ></ script >
2026-04-10 19:12:06 +08:00
< script defer src = "vendor/marked.min.js" ></ script >
2026-06-14 12:33:48 -04:00
<!-- DOMPurify (allowlist HTML sanitizer for rendered markdown).
Must load before sanitize-html.js (which wires it) and app.js (which calls it). -->
< script defer src = "vendor/dompurify.min.js" ></ script >
2026-02-16 21:00:55 +01:00
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
< script > if ( window . innerWidth < 768 || (( 'ontouchstart' in window || navigator . maxTouchPoints > 0 ) && window . innerWidth < 1024 )) document . documentElement . classList . add ( 'mobile-init' );</ script >
2026-06-13 19:46:50 +02:00
<!-- Synchronous skin selection — runs before first paint to prevent theme flash -->
2026-07-21 00:39:10 +08:00
< script > try { var s = localStorage . getItem ( 'codeman:skin' ), a = [ 'og' , 'daylight-green' , 'daylight-blue' , 'paper-gray' , 'solarized-light' , 'catppuccin-latte' , 'rose-pine-dawn' ]; if ( a . indexOf ( s ) < 0 ) s = 'daylight-blue' ; document . documentElement . dataset . skin = s ; window . __codemanSkin = s ;} catch ( e ){ document . documentElement . dataset . skin = 'daylight-blue' ; window . __codemanSkin = 'daylight-blue' ;}</ script >
2026-07-21 02:49:25 +08:00
<!-- Apply the saved per-device language before first paint. The full translation
layer loads below; setting lang/dir here prevents an English accessibility
tree from flashing while the deferred scripts start. -->
< script > try { var m = window . innerWidth < 768 || (( 'ontouchstart' in window || navigator . maxTouchPoints > 0 ) && window . innerWidth < 1024 ); var k = m ? 'codeman-app-settings-mobile' : 'codeman-app-settings' ; var l = JSON . parse ( localStorage . getItem ( k ) || '{}' ). language ; l = l === 'zh-CN' ? 'zh-CN' : 'en' ; document . documentElement . lang = l ; window . __codemanLanguage = l ;} catch ( e ){ document . documentElement . lang = 'en' ; window . __codemanLanguage = 'en' ;}</ script >
2026-02-18 11:59:42 +01:00
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
< style >
2026-07-21 00:39:10 +08:00
. loading-skeleton { display : flex ; flex-direction : column ; height : 100 vh ; height : 100 dvh ; background : var ( -- bg - dark , #11151c )}
. skeleton-header { height : 40 px ; background : var ( -- glass - bg , rgba ( 31 , 38 , 48 , 0.85 )); border-bottom : 1 px solid var ( -- glass - border , rgba ( 255 , 255 , 255 , 0.08 )); display : flex ; align-items : center ; padding : 0 12 px }
. skeleton-brand { color : var ( -- accent , #38b6f0 ); font-size : 14 px ; font-weight : 700 ; font-family : 'Manrope' , - apple-system , BlinkMacSystemFont , 'Segoe UI' , sans-serif ; opacity : .85 }
2026-02-18 11:59:42 +01:00
. skeleton-tabs { display : flex ; gap : 4 px ; margin-left : 16 px }
2026-07-21 00:39:10 +08:00
. skeleton-tab { width : 80 px ; height : 24 px ; background : var ( -- control - bg , rgba ( 255 , 255 , 255 , 0.04 )); border-radius : 6 px }
. skeleton-terminal { flex : 1 ; background : var ( -- term - bg , #161b23 )}
. skeleton-toolbar { height : 42 px ; background : var ( -- glass - bg , rgba ( 31 , 38 , 48 , 0.85 )); border-top : 1 px solid var ( -- glass - border , rgba ( 255 , 255 , 255 , 0.08 ))}
2026-02-18 11:59:42 +01:00
. app-loaded . loading-skeleton { display : none }
</ style >
2026-01-18 05:14:34 +01:00
</ head >
< body >
2026-02-18 11:59:42 +01:00
<!-- Loading skeleton — visible instantly before CSS/JS loads, hidden by app-loaded class -->
< div class = "loading-skeleton" >
< div class = "skeleton-header" >
2026-02-26 16:44:34 +01:00
< span class = "skeleton-brand" > Codeman</ span >
2026-02-18 11:59:42 +01:00
< div class = "skeleton-tabs" >< div class = "skeleton-tab" ></ div ></ div >
</ div >
< div class = "skeleton-terminal" ></ div >
< div class = "skeleton-toolbar" ></ div >
</ div >
2026-01-30 14:14:45 +01:00
<!-- Skip link for keyboard users -->
2026-06-08 15:41:46 +02:00
<!-- onclick scrolls/focuses directly: with <base href="/"> a bare href="#..." would
navigate to /#... (the dashboard) from a /session/:id solo window. -->
< a href = "#terminalContainer" class = "skip-link" onclick = "event.preventDefault(); var t=document.getElementById('terminalContainer'); if(t){t.scrollIntoView(); var f=t.querySelector('textarea,[tabindex]'); (f||t).focus&&(f||t).focus();}" > Skip to terminal</ a >
2026-01-18 05:14:34 +01:00
< div class = "app" >
2026-01-19 00:38:11 +01:00
<!-- Compact Header with Session Tabs -->
2026-01-18 05:14:34 +01:00
< header class = "header" >
2026-01-19 00:38:11 +01:00
< div class = "header-brand" >
2026-07-23 22:47:09 +02:00
< span class = "logo" onclick = "app.goHome()" title = "Go to main page"
>< span class = "logo-text" > Codeman</ span >< span class = "logo-compact" aria-hidden = "true" > C</ span ></ span
>
2026-01-19 00:38:11 +01:00
</ div >
<!-- Session Tabs -->
2026-01-30 14:14:45 +01:00
< div class = "session-tabs" id = "sessionTabs" role = "tablist" aria-label = "Session tabs" >
2026-01-18 05:14:34 +01:00
</ div >
2026-01-19 00:38:11 +01:00
2026-06-10 04:07:26 +02:00
<!-- Detached single-session window title (shown only in solo mode) -->
< div class = "solo-session-title" id = "soloSessionTitle" style = "display: none;" aria-live = "polite" ></ div >
2026-06-13 16:17:00 +08:00
< div class = "header-right" id = "headerRight" >
2026-07-20 17:35:40 +02:00
< button class = "btn-admin-panel btn-admin-panel--hidden" id = "adminPanelBtn" onclick = "window.codemanAdmin.openAdminPanel()" title = "Admin Panel (multi-user administration)" aria-label = "Open admin panel" >
< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></ svg >
< span > Admin Panel</ span >
</ button >
2026-06-10 04:07:26 +02:00
< button class = "btn-icon-header btn-solo-redock" id = "soloRedockBtn" style = "display: none;" onclick = "window.close()" title = "Re-dock to dashboard (close window)" aria-label = "Re-dock session to dashboard" > ⊞ </ button >
2026-03-08 16:06:15 +01:00
< button class = "tunnel-indicator" id = "tunnelIndicator" style = "display: none;" onclick = "app.toggleTunnelPanel()" title = "Cloudflare Tunnel" aria-label = "Tunnel status" >
< span class = "tunnel-dot" ></ span >
</ button >
2026-02-12 08:34:51 +01:00
< div class = "connection-indicator" id = "connectionIndicator" style = "display: none;" >
< span class = "connection-dot" id = "connectionDot" ></ span >
< span class = "connection-text" id = "connectionText" ></ span >
</ div >
2026-01-19 02:48:28 +01:00
< div class = "header-font-controls" >
2026-01-30 14:14:45 +01:00
< button class = "btn-icon-header btn-sm" onclick = "app.decreaseFontSize()" title = "Decrease font (Ctrl+-)" aria-label = "Decrease font size" > A-</ button >
< span class = "font-size-display" id = "fontSizeDisplay" aria-label = "Current font size" > 14</ span >
< button class = "btn-icon-header btn-sm" onclick = "app.increaseFontSize()" title = "Increase font (Ctrl++)" aria-label = "Increase font size" > A+</ button >
2026-01-19 02:48:28 +01:00
</ div >
2026-01-19 06:51:25 +01:00
< div class = "header-system-stats" id = "headerSystemStats" title = "System resource usage" >
2026-01-19 10:36:30 +01:00
< div class = "stat-item" >
< span class = "stat-label" > CPU</ span >
< div class = "stat-bar" >
< div class = "stat-bar-fill stat-bar-cpu" id = "statCpuBar" ></ div >
</ div >
< span class = "stat-value" id = "statCpu" > --%</ span >
</ div >
2026-01-24 19:57:28 +01:00
< div class = "stat-item" >
< span class = "stat-label" > MEM</ span >
< div class = "stat-bar" >
< div class = "stat-bar-fill stat-bar-mem" id = "statMemBar" ></ div >
</ div >
< span class = "stat-value" id = "statMem" > --</ span >
</ div >
2026-01-19 06:51:25 +01:00
</ div >
2026-06-17 23:19:54 +08:00
< button class = "btn-icon-header btn-redraw-terminal btn-redraw-terminal--hidden" onclick = "app.restoreTerminalSize()" title = "Redraw terminal to fit current screen (Ctrl+Shift+R)" aria-label = "Redraw terminal" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< polyline points = "1 4 1 10 7 10" />< polyline points = "23 20 23 14 17 14" />< path d = "M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" /></ svg ></ button >
2026-06-10 12:16:47 +02:00
< button class = "btn-icon-header btn-response-viewer-header btn-response-viewer-header--hidden" onclick = "app.toggleResponseViewer()" title = "View last response" aria-label = "View last response" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />< circle cx = "12" cy = "12" r = "3" /></ svg ></ button >
2026-07-19 17:53:14 +02:00
< button class = "btn-icon-header btn-away-digest btn-away-digest--hidden" onclick = "app.openAwayDigest()" title = "Away Digest" aria-label = "Open away digest" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M8 6h13" />< path d = "M8 12h13" />< path d = "M8 18h13" />< path d = "M3 6h.01" />< path d = "M3 12h.01" />< path d = "M3 18h.01" /></ svg ></ button >
< button class = "btn-icon-header btn-session-manager btn-session-manager--hidden" onclick = "app.openSessionManager()" title = "Session Manager" aria-label = "Open session manager" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< polyline points = "12 2 2 7 12 12 22 7 12 2" />< polyline points = "2 17 12 22 22 17" />< polyline points = "2 12 12 17 22 12" /></ svg ></ button >
2026-08-09 14:04:34 +02:00
< button class = "btn-icon-header btn-approvals btn-approvals--hidden" id = "approvalsBtn" onclick = "app.toggleApprovalsInbox()" title = "Approvals waiting on you" aria-label = "Open approvals inbox" aria-expanded = "false" >
< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />< path d = "M13.73 21a2 2 0 0 1-3.46 0" /></ svg >
< span class = "approvals-badge" id = "approvalsBadge" > 0</ span >
</ button >
2026-08-09 23:05:11 +02:00
< button class = "btn-icon-header btn-readmymind btn-readmymind--hidden" id = "readMyMindBtn" onclick = "app.openReadMyMind()" title = "Read My Mind: predict your next prompt" aria-label = "Predict my next prompt" >< span class = "readmymind-icon" aria-hidden = "true" > 🧠</ span ></ button >
2026-06-14 21:21:06 +02:00
< button class = "btn-icon-header btn-attachments-history btn-attachments-history--hidden" id = "attachmentsHistoryBtn" onclick = "app.toggleAttachmentHistory()" title = "Attachments" aria-label = "Open attachment history" aria-expanded = "false" >
2026-06-12 14:37:27 -04:00
< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" /></ svg >
< span class = "attachment-history-badge" id = "attachmentHistoryBadge" style = "display:none;" > 0</ span >
</ button >
2026-07-24 14:50:43 +02:00
< button class = "btn-icon-header btn-file-viewer" onclick = "app.toggleFileBrowserButton()" title = "File Viewer" aria-label = "Open file viewer" aria-expanded = "false" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /></ svg ></ button >
2026-06-08 15:46:10 +02:00
< button class = "btn-icon-header btn-multimonitor btn-multimonitor--hidden" onclick = "app.launchMultiMonitor()" title = "Open Codeman across all displays" aria-label = "Open Codeman across all displays" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< rect x = "2" y = "4" width = "13" height = "9" rx = "1.5" />< rect x = "11" y = "9" width = "11" height = "8" rx = "1.5" /></ svg ></ button >
2026-06-15 08:40:05 +02:00
< button class = "btn-icon-header btn-ultracode-agents btn-ultracode-agents--hidden" onclick = "app.toggleUltracodeAgentsPanel()" title = "Ultracode / Workflow agents" aria-label = "Open ultracode workflow agents" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< circle cx = "6" cy = "6" r = "2.5" />< circle cx = "6" cy = "18" r = "2.5" />< circle cx = "18" cy = "12" r = "2.5" />< path d = "M8.2 7.2 15.6 11M8.2 16.8 15.6 13" /></ svg ></ button >
2026-06-14 05:00:28 +02:00
< div class = "header-plan-usage header-plan-usage--hidden" id = "planUsageChip" title = "Claude plan usage limits" > —</ div >
2026-06-08 04:53:33 +02:00
< button class = "btn-icon-header btn-notifications" onclick = "app.toggleNotifications()" title = "Notifications" aria-label = "Toggle notifications" style = "display:none;" >
2026-01-30 14:14:45 +01:00
< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />< path d = "M13.73 21a2 2 0 0 1-3.46 0" /></ svg >
2026-01-24 01:22:53 +01:00
< span class = "notification-badge" id = "notifBadge" style = "display:none;" > 0</ span >
</ button >
2026-07-24 14:50:43 +02:00
< button class = "btn-icon-header btn-lifecycle-log" style = "display: none" onclick = "app.openLifecycleLog()" title = "Session Lifecycle Log" aria-label = "Open session lifecycle log" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />< polyline points = "14 2 14 8 20 8" />< line x1 = "16" y1 = "13" x2 = "8" y2 = "13" />< line x1 = "16" y1 = "17" x2 = "8" y2 = "17" />< polyline points = "10 9 9 9 8 9" /></ svg ></ button >
2026-01-30 14:14:45 +01:00
< button class = "btn-icon-header btn-settings" onclick = "app.openAppSettings()" title = "App Settings" aria-label = "Open app settings" >< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< circle cx = "12" cy = "12" r = "3" />< path d = "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" /></ svg ></ button >
2026-07-24 14:50:43 +02:00
< div class = "header-tokens" id = "headerTokens" style = "display: none" title = "Total tokens across all sessions" > 0 tokens</ div >
2026-01-18 05:14:34 +01:00
</ div >
</ header >
2026-08-09 15:55:35 +02:00
<!-- Connection-loss banner: shown once session state HAS loaded and the
link then drops, so the terminal stays readable behind it.
The blocking variant is #offlineOverlay at the end of <body>. -->
< div class = "offline-banner" id = "offlineBanner" role = "status" hidden >
< span class = "offline-banner-dot" aria-hidden = "true" ></ span >
< span class = "offline-banner-text" id = "offlineBannerText" > No connection to the Codeman server</ span >
< span class = "offline-banner-detail" id = "offlineBannerDetail" ></ span >
< button class = "offline-banner-retry" id = "offlineBannerRetry" onclick = "app.retryConnection()" > Retry now</ button >
</ div >
2026-01-18 05:14:34 +01:00
<!-- Timer Banner (shown when timed run is active) -->
< div class = "timer-banner" id = "timerBanner" style = "display: none;" >
2026-01-19 00:38:11 +01:00
< div class = "timer-content" >
2026-01-18 05:14:34 +01:00
< span class = "timer-value" id = "timerValue" > 00:00:00</ span >
2026-01-19 00:38:11 +01:00
< div class = "timer-progress" >< div class = "timer-progress-fill" id = "timerProgress" ></ div ></ div >
< span class = "timer-meta" id = "timerMeta" > 0 tasks | $0.00</ span >
2026-01-18 05:14:34 +01:00
</ div >
2026-01-30 14:14:45 +01:00
< button class = "btn-icon-only" onclick = "app.stopCurrentRun()" title = "Stop" aria-label = "Stop current run" > ■ </ button >
2026-01-18 05:14:34 +01:00
</ div >
2026-01-26 02:21:34 +01:00
<!-- Respawn Banner - Compact 2-column layout -->
2026-01-18 07:31:19 +01:00
< div class = "respawn-banner" id = "respawnBanner" style = "display: none;" >
2026-01-26 02:21:34 +01:00
< div class = "respawn-compact-layout" >
< div class = "respawn-status-col" >
< div class = "respawn-status-row1" >
< span class = "respawn-indicator" > ↻ </ span >
< span class = "respawn-state" id = "respawnState" > Watching</ span >
< span class = "detection-confidence" id = "detectionConfidence" ></ span >
< span class = "respawn-cycles" > #< span id = "respawnCycleCount" > 0</ span ></ span >
< span class = "respawn-timer" id = "respawnTimer" style = "display: none;" ></ span >
< span class = "respawn-tokens" id = "respawnTokens" style = "display: none;" ></ span >
2026-01-30 14:14:45 +01:00
< button class = "btn-icon-only" onclick = "app.stopRespawn()" title = "Stop Respawn" aria-label = "Stop respawn" > ■ </ button >
2026-01-26 02:21:34 +01:00
</ div >
< div class = "respawn-status-row2" id = "respawnStatusRow2" >
< span class = "detection-hook" id = "detectionHook" style = "display: none;" ></ span >
< span class = "detection-ai-check" id = "detectionAiCheck" style = "display: none;" ></ span >
< span class = "detection-status" id = "detectionStatus" ></ span >
< span class = "detection-waiting" id = "detectionWaiting" style = "display: none;" ></ span >
< div class = "respawn-countdown-timers" id = "respawnCountdownTimers" ></ div >
</ div >
</ div >
2026-01-25 04:57:09 +01:00
< div class = "respawn-action-log" id = "respawnActionLog" ></ div >
2026-01-25 03:57:44 +01:00
</ div >
2026-01-18 07:31:19 +01:00
</ div >
2026-03-21 07:20:18 +01:00
<!-- Orchestrator Loop Panel -->
< div class = "orchestrator-panel" id = "orchestratorPanel" style = "display: none;" >
< div class = "orchestrator-header" >
< span class = "orchestrator-title" > Orchestrator</ span >
< span class = "orchestrator-state-badge" id = "orchestratorStateBadge" > idle</ span >
< div class = "orchestrator-actions" id = "orchestratorActions" ></ div >
< button class = "orchestrator-close-btn" onclick = "app.closeOrchestratorPanel()" title = "Close" > × </ button >
</ div >
< div class = "orchestrator-body" id = "orchestratorBody" ></ div >
</ div >
2026-01-21 22:36:45 +01:00
<!-- Ralph / Todo Tracker Panel -->
< div class = "ralph-panel collapsed" id = "ralphStatePanel" style = "display: none;" >
2026-01-19 17:09:56 +01:00
<!-- Collapsed Summary Bar -->
2026-01-19 18:41:14 +01:00
< div class = "ralph-summary" id = "ralphSummary" >
2026-01-21 22:36:45 +01:00
< div class = "ralph-summary-content" onclick = "app.toggleRalphStatePanel()" >
2026-01-19 18:41:14 +01:00
< div class = "ralph-ring-mini" id = "ralphRingMini" >
< svg viewBox = "0 0 36 36" class = "ralph-ring-svg" >
2026-01-20 20:00:57 +01:00
< defs >
< linearGradient id = "ralphGradientMini" x1 = "0%" y1 = "0%" x2 = "100%" y2 = "100%" >
2026-06-13 19:46:50 +02:00
< stop offset = "0%" stop-color = "#3ec8ee" />
< stop offset = "100%" stop-color = "#2b8fd9" />
2026-01-20 20:00:57 +01:00
</ linearGradient >
</ defs >
2026-01-19 18:41:14 +01:00
< circle class = "ralph-ring-bg" cx = "18" cy = "18" r = "15.9" />
2026-01-27 11:02:02 +01:00
< circle class = "ralph-ring-progress-mini" cx = "18" cy = "18" r = "15.9" id = "ralphRingMiniProgress" style = "stroke-dashoffset: 100" />
2026-01-19 18:41:14 +01:00
</ svg >
< span class = "ralph-ring-text" id = "ralphRingMiniText" > 0%</ span >
</ div >
< div class = "ralph-summary-info" >
2026-01-21 22:36:45 +01:00
< span class = "ralph-title" > Ralph / Todo Tracker</ span >
2026-01-19 18:41:14 +01:00
< span class = "ralph-status-badge" id = "ralphStatusBadge" >
< span class = "ralph-status-dot" ></ span >
< span class = "ralph-status-text" > Idle</ span >
</ span >
</ div >
< div class = "ralph-summary-stats" >
< span class = "ralph-stat" >< span class = "ralph-stat-icon" > ⏱ </ span >< span id = "ralphStatTime" > 0m</ span ></ span >
< span class = "ralph-stat" >< span class = "ralph-stat-icon" > ✓ </ span >< span id = "ralphStatTasks" > 0/0</ span ></ span >
</ div >
2026-01-20 18:56:55 +01:00
< span class = "ralph-toggle" id = "ralphToggle" > ▼ </ span >
2026-01-19 17:09:56 +01:00
</ div >
2026-01-19 18:41:14 +01:00
< div class = "ralph-controls" >
2026-01-27 01:02:38 +01:00
< div class = "ralph-menu-container" >
< button class = "btn-icon-sm" onclick = "event.stopPropagation(); app.toggleRalphMenu()" title = "Fix Plan Menu" id = "ralphMenuBtn" > ☰ </ button >
< div class = "ralph-dropdown" id = "ralphDropdown" >
< button onclick = "event.stopPropagation(); app.showFixPlan(); app.closeRalphMenu()" > View Fix Plan</ button >
< button onclick = "event.stopPropagation(); app.writeFixPlanToFile(); app.closeRalphMenu()" > Write to File</ button >
< button onclick = "event.stopPropagation(); app.importFixPlanFromFile(); app.closeRalphMenu()" > Import from File</ button >
< hr >
< button onclick = "event.stopPropagation(); app.resetCircuitBreaker(); app.closeRalphMenu()" > Reset Circuit Breaker</ button >
</ div >
</ div >
2026-01-20 17:37:49 +01:00
< button class = "btn-icon-sm" onclick = "event.stopPropagation(); app.toggleRalphDetach()" title = "Detach/Attach panel" id = "ralphDetachBtn" > ⧉ </ button >
2026-01-20 17:54:59 +01:00
< button class = "btn-icon-sm" onclick = "event.stopPropagation(); app.closeRalphTracker()" title = "Close tracker" > × </ button >
2026-01-19 17:09:56 +01:00
</ div >
</ div >
2026-01-20 17:59:34 +01:00
<!-- Expanded Detail View - Compact horizontal layout -->
2026-01-19 17:09:56 +01:00
< div class = "ralph-detail" id = "ralphDetail" >
2026-01-20 17:59:34 +01:00
< div class = "ralph-detail-row" >
<!-- Progress Ring -->
< div class = "ralph-ring-compact" >
2026-01-19 17:09:56 +01:00
< svg viewBox = "0 0 100 100" class = "ralph-ring-svg-large" >
< defs >
< linearGradient id = "ralphGradient" x1 = "0%" y1 = "0%" x2 = "100%" y2 = "100%" >
2026-06-13 19:46:50 +02:00
< stop offset = "0%" stop-color = "#3ec8ee" />
< stop offset = "100%" stop-color = "#2b8fd9" />
2026-01-19 17:09:56 +01:00
</ linearGradient >
</ defs >
< circle class = "ralph-ring-track" cx = "50" cy = "50" r = "42" />
2026-01-27 11:02:02 +01:00
< circle class = "ralph-ring-fill" cx = "50" cy = "50" r = "42" id = "ralphRingProgress" style = "stroke-dashoffset: 264" />
2026-01-19 17:09:56 +01:00
</ svg >
< div class = "ralph-ring-center" >
< div class = "ralph-ring-percent" id = "ralphRingPercent" > 0%</ div >
< div class = "ralph-ring-label" > Complete</ div >
</ div >
</ div >
2026-01-20 20:26:04 +01:00
<!-- Meta Info + Tasks unified -->
< div class = "ralph-info-unified" >
< div class = "ralph-info-labels" >
< div class = "ralph-info-row" >
2026-01-20 20:35:00 +01:00
< span class = "ralph-info-label" > Tasks</ span >
< span class = "ralph-tasks-count" id = "ralphTasksCount" > 0/0</ span >
</ div >
< div class = "ralph-info-row" >
< span class = "ralph-info-label" > Phrase</ span >
2026-01-20 20:26:04 +01:00
< code id = "ralphPhrase" > --</ code >
</ div >
< div class = "ralph-info-row" >
< span class = "ralph-info-label" > Elapsed</ span >
< span id = "ralphElapsed" > 0m</ span >
</ div >
2026-01-27 23:15:25 +01:00
< div class = "ralph-info-row" id = "ralphVersionRow" style = "display: none;" >
< span class = "ralph-info-label" > Plan</ span >
< div class = "ralph-version-info" >
< span class = "version-badge" id = "ralphPlanVersion" > v1</ span >
< button class = "rollback-btn" id = "ralphRollbackBtn" onclick = "app.showPlanHistory()" title = "View history" > history</ button >
</ div >
</ div >
2026-01-20 17:59:34 +01:00
</ div >
< div class = "ralph-tasks-grid" id = "ralphTasksGrid" ></ div >
2026-01-19 17:09:56 +01:00
</ div >
</ div >
</ div >
2026-01-19 13:21:01 +01:00
</ div >
2026-01-19 00:38:11 +01:00
<!-- Main Terminal Area -->
2026-01-18 05:14:34 +01:00
< main class = "main" >
2026-03-14 16:23:25 +09:00
< div class = "terminal-wrap" >
< div class = "terminal-container" id = "terminalContainer" ></ div >
2026-03-14 18:27:58 +01:00
< textarea id = "cjkInput" rows = "1" placeholder = "CJK input (Enter = send, Esc = clear)"
maxlength = "65536" aria-label = "CJK IME input field"
2026-03-14 16:23:25 +09:00
autocomplete = "off" autocorrect = "off" autocapitalize = "off" spellcheck = "false" ></ textarea >
</ div >
2026-01-29 12:47:23 +01:00
2026-07-27 17:06:36 +02:00
<!-- Web tab layer: one iframe per open dashboard, shown in place of the
terminal while a web tab is active. Frames stay mounted while hidden so
switching tabs does not reload (and re-authenticate) a dashboard. -->
< div class = "webview-layer" id = "webviewLayer" ></ div >
2026-01-29 12:47:23 +01:00
<!-- Welcome Overlay (shown when no session active) -->
< div class = "welcome-overlay" id = "welcomeOverlay" >
2026-08-10 02:56:35 +02:00
<!-- Open tabs as a rail docked to the left edge (home-sessions.js).
2026-08-09 18:17:42 +02:00
Absolutely positioned so the centered content below never moves, and
2026-08-10 02:56:35 +02:00
therefore only rendered where the gutter is wider than the rail;
2026-08-09 18:17:42 +02:00
ships `hidden` and only that module reveals it. -->
< aside class = "home-sessions" id = "homeSessions" hidden ></ aside >
2026-01-29 12:47:23 +01:00
< div class = "welcome-content" >
2026-02-26 16:44:34 +01:00
< h1 class = "welcome-title" > Codeman</ h1 >
2026-02-26 16:12:41 +01:00
< p class = "welcome-desc" > Manage AI Coding tools in persistent tmux sessions.</ p >
2026-01-29 12:47:23 +01:00
< div class = "welcome-actions" >
2026-08-01 21:55:33 -05:00
< button class = "welcome-btn welcome-btn-claude" id = "welcomeClaudeBtn" style = "display: none;" onclick = "app.setRunMode('claude'); app.runClaude()" >
2026-01-29 12:47:23 +01:00
< svg width = "20" height = "20" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< polygon points = "5 3 19 12 5 21 5 3" /></ svg >
2026-02-26 16:07:33 +01:00
Run Claude Code
2026-02-26 10:16:20 +01:00
</ button >
2026-08-05 01:53:43 +02:00
< button class = "welcome-btn welcome-btn-tunnel" id = "welcomeTunnelBtn" style = "display: none;" onclick = "app.toggleTunnelFromWelcome()" >
< svg width = "20" height = "20" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M12 2L2 7l10 5 10-5-10-5z" />< path d = "M2 17l10 5 10-5" />< path d = "M2 12l10 5 10-5" /></ svg >
Cloudflare Tunnel
</ button >
2026-08-01 21:55:33 -05:00
< button class = "welcome-btn welcome-btn-opencode" id = "welcomeOpencodeBtn" style = "display: none;" onclick = "app.setRunMode('opencode'); app.runOpenCode()" >
2026-02-26 16:07:33 +01:00
< svg width = "20" height = "20" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< polygon points = "5 3 19 12 5 21 5 3" /></ svg >
Run OpenCode
</ button >
2026-08-06 07:21:57 +02:00
< button class = "welcome-btn welcome-btn-antigravity" id = "welcomeAntigravityBtn" style = "display: none;" onclick = "app.setRunMode('antigravity'); app.runAntigravity()" >
< svg width = "20" height = "20" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< polygon points = "5 3 19 12 5 21 5 3" /></ svg >
Run Antigravity
</ button >
2026-07-31 11:20:15 -05:00
< button class = "welcome-btn welcome-btn-gemini" id = "welcomeGeminiBtn" style = "display: none;" onclick = "app.setRunMode('gemini'); app.runGemini()" >
2026-05-16 20:10:49 -04:00
< svg width = "20" height = "20" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< polygon points = "5 3 19 12 5 21 5 3" /></ svg >
Run Gemini
</ button >
2026-02-26 16:07:33 +01:00
</ div >
2026-08-05 01:53:43 +02:00
< div class = "welcome-qr" id = "welcomeQr" onclick = "app.toggleWelcomeQrSize()" >
< div class = "welcome-qr-inner" id = "welcomeQrInner" ></ div >
< div class = "welcome-qr-url" id = "welcomeQrUrl" ></ div >
</ div >
2026-03-10 02:23:02 +08:00
< div class = "history-sessions" id = "historySessions" style = "display:none" >
2026-06-13 19:20:06 -04:00
< div class = "search-panel" id = "searchPanel" >
< div class = "search-input-row" >
< input
type = "search"
id = "searchInput"
class = "search-input"
placeholder = "Search sessions, events, files…"
autocomplete = "off"
spellcheck = "false"
maxlength = "200"
aria-label = "Search across sessions"
/>
< button type = "button" id = "searchClearBtn" class = "search-clear-btn" aria-label = "Clear search" hidden > × </ button >
</ div >
< div class = "search-filters" id = "searchFilters" >
< div class = "search-filter-group" role = "group" aria-label = "Source type filter" >
< button type = "button" class = "search-filter-chip active" data-type-filter = "session" > Sessions</ button >
< button type = "button" class = "search-filter-chip active" data-type-filter = "event" > Events</ button >
< button type = "button" class = "search-filter-chip active" data-type-filter = "file" > Files</ button >
</ div >
< div class = "search-filter-group search-filter-secondary" >
< select id = "searchCaseFilter" class = "search-select" aria-label = "Filter by case" >
< option value = "" > All cases</ option >
</ select >
< select id = "searchStatusFilter" class = "search-select" aria-label = "Filter by session status" >
< option value = "" > Any status</ option >
< option value = "active" > Active</ option >
< option value = "history" > History</ option >
</ select >
< select id = "searchDateFilter" class = "search-select" aria-label = "Filter by date range" >
< option value = "" > Any time</ option >
< option value = "1" > Past 24h</ option >
< option value = "7" > Past 7 days</ option >
< option value = "30" > Past 30 days</ option >
</ select >
</ div >
</ div >
< div class = "search-results" id = "searchResults" hidden ></ div >
</ div >
2026-08-10 03:02:11 +02:00
< div class = "history-header" id = "historyHeader" >
< h3 class = "history-title" id = "historyTitle" > Resume Conversation</ h3 >
< span class = "history-count" id = "historyCount" data-i18n-skip ></ span >
< div class = "history-controls" >
< input
type = "search"
id = "historyFilter"
class = "history-filter"
placeholder = "Filter…"
autocomplete = "off"
spellcheck = "false"
maxlength = "100"
aria-label = "Filter past conversations"
/>
< select id = "historySort" class = "search-select history-sort" aria-label = "Sort past conversations" >
< option value = "recent" > Recent</ option >
< option value = "name" > Name A– Z</ option >
< option value = "folder" > Folder A– Z</ option >
</ select >
</ div >
</ div >
2026-03-10 02:23:02 +08:00
< div class = "history-list" id = "historyList" ></ div >
</ div >
2026-07-09 11:26:24 -04:00
< p class = "welcome-hint" > Or click Run to start</ p >
2026-02-27 10:58:10 +01:00
< button class = "welcome-ralph-link" onclick = "app.showRalphWizard()" > Start Ralph Loop → </ button >
2026-01-29 12:47:23 +01:00
</ div >
</ div >
2026-08-04 15:02:46 +02:00
<!-- Mobile Overview (phone home screen, shown in place of the welcome
overlay when the "C" logo is tapped). Ships `hidden`: only
mobile-overview.js removes it, and only when the phone gate passes, so
desktop (which never loads mobile.css) can never render it. -->
< div class = "mobile-overview" id = "mobileOverview" hidden ></ div >
2026-01-18 05:14:34 +01:00
</ main >
2026-01-26 00:15:11 +01:00
<!-- Project Insights Panel (shows file-viewing Bash commands) -->
< div class = "project-insights-panel" id = "projectInsightsPanel" >
< div class = "project-insights-header" >
< span class = "project-insights-title" > Project Insights</ span >
< button class = "btn-icon-sm" onclick = "app.closeProjectInsightsPanel()" title = "Close" > × </ button >
2026-01-25 23:41:59 +01:00
</ div >
2026-01-26 00:15:11 +01:00
< div class = "project-insights-body" id = "projectInsightsList" ></ div >
< div class = "project-insights-resize-handle" ></ div >
2026-01-25 23:41:59 +01:00
</ div >
2026-01-27 01:02:38 +01:00
<!-- File Browser Panel -->
< div class = "file-browser-panel" id = "fileBrowserPanel" >
< div class = "file-browser-header" >
< span class = "file-browser-title" > Files</ span >
< div class = "file-browser-actions" >
2026-08-09 15:44:20 +02:00
< button class = "btn-icon-sm btn-file-browser-hidden" onclick = "app.toggleFileBrowserHidden()" title = "Show hidden files and folders" aria-label = "Show hidden files and folders" aria-pressed = "false" id = "fileBrowserHiddenBtn" > .*</ button >
2026-01-27 01:02:38 +01:00
< button class = "btn-icon-sm" onclick = "app.refreshFileBrowser()" title = "Refresh" > ↻ </ button >
< button class = "btn-icon-sm" onclick = "app.toggleFileBrowserExpand()" title = "Expand/Collapse All" id = "fileBrowserExpandBtn" > ⊞ </ button >
< button class = "btn-icon-sm" onclick = "app.closeFileBrowserPanel()" title = "Close" > × </ button >
</ div >
</ div >
< div class = "file-browser-search" >
< input type = "text" id = "fileBrowserSearch" placeholder = "Filter files..." oninput = "app.filterFileBrowser(this.value)" >
</ div >
< div class = "file-browser-body" id = "fileBrowserTree" >
< div class = "file-browser-empty" > Select a session to view files</ div >
</ div >
< div class = "file-browser-status" id = "fileBrowserStatus" ></ div >
</ div >
<!-- File Preview Overlay -->
< div class = "file-preview-overlay" id = "filePreviewOverlay" >
< div class = "file-preview-window" >
< div class = "file-preview-header" >
< span class = "file-preview-title" id = "filePreviewTitle" > file.ts</ span >
< div class = "file-preview-actions" >
2026-08-05 08:44:47 +02:00
< button class = "btn-icon-sm file-preview-edit-btn" id = "filePreviewEditBtn" onclick = "app.enterFilePreviewEdit()" title = "Edit file" aria-label = "Edit file" hidden >< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z" /></ svg ></ button >
2026-01-27 01:02:38 +01:00
< button class = "btn-icon-sm" onclick = "app.copyFilePreviewContent()" title = "Copy content" > ⎘ </ button >
< button class = "btn-icon-sm" onclick = "app.closeFilePreview()" title = "Close" > × </ button >
</ div >
</ div >
< div class = "file-preview-body" id = "filePreviewBody" ></ div >
2026-08-05 08:44:47 +02:00
< div class = "file-preview-editbar" id = "filePreviewEditBar" hidden >
< span class = "file-preview-dirty" id = "filePreviewDirty" hidden > Unsaved changes</ span >
< span class = "file-preview-editbar-spacer" ></ span >
< button class = "file-preview-editbar-btn" onclick = "app.cancelFilePreviewEdit()" > Cancel</ button >
< button class = "file-preview-editbar-btn file-preview-editbar-btn--save" id = "filePreviewSaveBtn" onclick = "app.saveFilePreviewEdit()" disabled > Save</ button >
</ div >
2026-01-27 01:02:38 +01:00
< div class = "file-preview-footer" id = "filePreviewFooter" ></ div >
</ div >
</ div >
2026-04-10 19:07:02 +08:00
<!-- Response Viewer (mobile: native-scroll overlay for reading full Claude responses) -->
< div class = "response-viewer" id = "responseViewer" >
< div class = "response-viewer-header" >
< span id = "responseViewerTitle" > Last Response</ span >
< div class = "response-viewer-actions" >
< button class = "response-viewer-more" id = "responseViewerMore" onclick = "app.loadFullContext()" > More</ button >
< button class = "response-viewer-close" onclick = "app.toggleResponseViewer()" > × </ button >
</ div >
</ div >
< div class = "response-viewer-body" id = "responseViewerBody" ></ div >
</ div >
< div class = "response-viewer-backdrop" id = "responseViewerBackdrop" onclick = "app.toggleResponseViewer()" ></ div >
2026-01-19 00:38:11 +01:00
<!-- Bottom Toolbar -->
< footer class = "toolbar" >
< div class = "toolbar-left" >
2026-02-26 12:04:57 +01:00
<!-- Run AI -->
2026-01-19 00:38:11 +01:00
< div class = "toolbar-group" >
2026-02-26 12:04:57 +01:00
< div class = "run-btn-group" >
2026-07-09 11:26:24 -04:00
< button class = "btn-toolbar btn-run" id = "runBtn" onclick = "app.run()" title = "Run" >
2026-02-26 12:04:57 +01:00
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2.5" >< polygon points = "5 3 19 12 5 21 5 3" /></ svg >
< span id = "runBtnLabel" > Run</ span >
</ button >
< button class = "btn-toolbar btn-run-gear" onclick = "app.toggleRunModeMenu(event)" title = "Select AI backend" >
< svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M6 9l6 6 6-6" /></ svg >
</ button >
< div class = "run-mode-menu" id = "runModeMenu" >
< button class = "run-mode-option" data-mode = "claude" onclick = "app.setRunMode('claude')" >
< span class = "run-mode-dot claude" ></ span > Claude Code
</ button >
< button class = "run-mode-option" data-mode = "opencode" onclick = "app.setRunMode('opencode')" >
< span class = "run-mode-dot opencode" ></ span > OpenCode
</ button >
2026-06-10 09:31:58 -04:00
< button class = "run-mode-option" data-mode = "codex" onclick = "app.setRunMode('codex')" >
< span class = "run-mode-dot codex" ></ span > Codex
</ button >
2026-05-16 20:10:49 -04:00
< button class = "run-mode-option" data-mode = "gemini" onclick = "app.setRunMode('gemini')" >
< span class = "run-mode-dot gemini" ></ span > Gemini
</ button >
2026-08-04 12:59:40 +02:00
< button class = "run-mode-option" data-mode = "antigravity" onclick = "app.setRunMode('antigravity')" >
< span class = "run-mode-dot antigravity" ></ span > Antigravity
</ button >
2026-03-10 14:03:19 +08:00
< div class = "run-mode-sep" ></ div >
2026-07-27 03:01:14 +02:00
< button class = "run-mode-option" data-mode = "shell" onclick = "app.setRunMode('shell')" >
< span class = "run-mode-dot shell" ></ span > Terminal / Shell
</ button >
< div class = "run-mode-sep" ></ div >
2026-07-27 17:06:36 +02:00
<!-- Web tabs: dashboards open as tabs beside agent sessions. These do NOT
set runMode: the Run button always means "start an agent". -->
< div class = "run-mode-header" > Web / URL</ div >
< div class = "run-mode-webviews" id = "runModeWebviews" ></ div >
< button class = "run-mode-option run-mode-option--add" onclick = "app.showWebviewModal()" >
< span class = "run-mode-dot web" ></ span > Add URL…
</ button >
< div class = "run-mode-sep" ></ div >
2026-03-10 14:03:19 +08:00
< div class = "run-mode-header" > Recent Sessions</ div >
< div class = "run-mode-history" id = "runModeHistory" ></ div >
2026-02-26 12:04:57 +01:00
</ div >
</ div >
2026-01-31 07:17:02 +01:00
< div class = "tab-count-group" title = "Instance count" >
< button class = "tab-count-btn" onclick = "app.decrementTabCount()" > − </ button >
< input type = "number" id = "tabCount" class = "tab-count-input" value = "1" min = "1" max = "20" readonly >
< button class = "tab-count-btn" onclick = "app.incrementTabCount()" > +</ button >
</ div >
2026-02-17 21:27:43 +01:00
< button class = "btn-toolbar btn-stop" onclick = "app.stopClaude()" title = "Stop (Ctrl+C)" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "currentColor" >< rect x = "4" y = "4" width = "16" height = "16" rx = "2" /></ svg >
</ button >
2026-01-19 10:36:30 +01:00
< button class = "btn-toolbar btn-shell" onclick = "app.runShell()" title = "Run Shell" >
Run Shell
2026-01-19 00:38:11 +01:00
</ button >
2026-07-27 03:01:14 +02:00
<!-- Phone-only: replaces the Shell button on ≤430px (Shell moves into the Run
dropdown there). Sends a bare Enter to the active session, the complement
to the accessory bar's Esc. Hidden everywhere else — see styles.css. -->
< button class = "btn-toolbar btn-enter" onclick = "app.sendEnterKey()" title = "Send Enter" >
Enter
</ button >
2026-01-31 07:17:02 +01:00
< div class = "tab-count-group" title = "Instance count" >
< button class = "tab-count-btn" onclick = "app.decrementShellCount()" > − </ button >
< input type = "number" id = "shellCount" class = "tab-count-input" value = "1" min = "1" max = "20" readonly >
< button class = "tab-count-btn" onclick = "app.incrementShellCount()" > +</ button >
</ div >
< div class = "case-select-group" >
2026-07-01 09:35:51 +00:00
< div class = "case-combobox" id = "quickStartCasePicker" >
< input
type = "text"
id = "quickStartCaseSearch"
class = "case-combobox-input"
role = "combobox"
aria-controls = "quickStartCaseList"
aria-expanded = "false"
aria-autocomplete = "list"
autocomplete = "off"
spellcheck = "false"
title = "Select case"
>
< div id = "quickStartCaseList" class = "case-combobox-list hidden" role = "listbox" ></ div >
</ div >
< select id = "quickStartCase" class = "toolbar-select case-native-select" title = "Select case" aria-hidden = "true" tabindex = "-1" >
2026-01-31 07:17:02 +01:00
< option value = "testcase" > testcase</ option >
</ select >
< button class = "btn-case-add" onclick = "app.showCreateCaseModal()" title = "Create new case" > +</ button >
2026-02-12 08:34:51 +01:00
< button class = "btn-case-settings" onclick = "app.toggleCaseSettings()" title = "Case settings" > ⚙ </ button >
< div class = "case-settings-popover hidden" id = "caseSettingsPopover" >
< label class = "checkbox-inline" >
< input type = "checkbox" id = "caseAgentTeams" onchange = "app.onCaseSettingChanged()" >
< span > Agent Teams</ span >
</ label >
< span class = "form-hint" > Enable experimental Agent Teams for new sessions in this case</ span >
2026-03-28 16:49:55 +01:00
< label class = "checkbox-inline" style = "margin-top: 6px;" >
< input type = "checkbox" id = "caseOpusContext1m" onchange = "app.onCaseSettingChanged()" >
< span > 1M Opus Context</ span >
</ label >
< span class = "form-hint" > Use 1M token context window for new sessions</ span >
2026-02-12 08:34:51 +01:00
</ div >
2026-01-31 07:17:02 +01:00
</ div >
2026-02-16 16:45:47 +01:00
<!-- Mobile-only case button + gear -->
2026-02-25 09:07:10 +01:00
< button class = "btn-voice-mobile" id = "voiceInputBtnMobile" onclick = "VoiceInput.toggle()"
title = "Voice input" aria-label = "Start voice input" aria-pressed = "false" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >
< path d = "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
< path d = "M19 10v2a7 7 0 0 1-14 0v-2" />
< line x1 = "12" y1 = "19" x2 = "12" y2 = "23" />
< line x1 = "8" y1 = "23" x2 = "16" y2 = "23" />
</ svg >
</ button >
2026-02-16 20:47:50 +01:00
< button class = "btn-settings-mobile" onclick = "app.openAppSettings()" title = "App Settings" > ⚙ </ button >
2026-01-31 22:17:47 +01:00
< button class = "btn-toolbar btn-case-mobile" onclick = "app.showMobileCasePicker()" title = "Select case" >
< svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" /></ svg >
< span id = "mobileCaseName" > Case</ span >
</ button >
2026-02-16 20:47:50 +01:00
< button class = "btn-case-settings-mobile" id = "btnCaseSettingsMobile" onclick = "app.toggleCaseSettingsMobile()" title = "Case settings" >< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< circle cx = "12" cy = "12" r = "3" />< path d = "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" /></ svg ></ button >
2026-02-16 16:45:47 +01:00
< div class = "case-settings-popover-mobile hidden" id = "caseSettingsPopoverMobile" >
< label class = "checkbox-inline" >
< input type = "checkbox" id = "caseAgentTeamsMobile" onchange = "app.onCaseSettingChangedMobile()" >
< span > Agent Teams</ span >
</ label >
< span class = "form-hint" > Enable Agent Teams for new sessions</ span >
2026-03-28 16:49:55 +01:00
< label class = "checkbox-inline" style = "margin-top: 6px;" >
< input type = "checkbox" id = "caseOpusContext1mMobile" onchange = "app.onCaseSettingChangedMobile()" >
< span > 1M Opus Context</ span >
</ label >
< span class = "form-hint" > Use 1M token context window</ span >
2026-02-16 16:45:47 +01:00
</ div >
2026-01-31 07:17:02 +01:00
</ div >
<!-- Directory (hidden) -->
< div class = "toolbar-group" style = "display: none;" >
< button class = "btn-toolbar" onclick = "app.toggleDirInput()" title = "Set working directory" >
< span id = "dirDisplay" > No directory</ span >
</ button >
< input type = "text" id = "dirInput" class = "toolbar-input hidden" placeholder = "Working directory..." onblur = "app.hideDirInput()" >
2026-01-18 07:31:19 +01:00
</ div >
2026-01-19 00:38:11 +01:00
</ div >
2026-01-25 04:04:24 +01:00
< div class = "toolbar-center" >
2026-02-25 00:32:45 +01:00
< button class = "btn-toolbar btn-sm btn-voice" id = "voiceInputBtn" onclick = "VoiceInput.toggle()"
2026-02-25 09:07:10 +01:00
title = "Voice input (Ctrl+Shift+V)" aria-label = "Start voice input" aria-pressed = "false" >
2026-02-25 00:32:45 +01:00
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >
< path d = "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
< path d = "M19 10v2a7 7 0 0 1-14 0v-2" />
< line x1 = "12" y1 = "19" x2 = "12" y2 = "23" />
< line x1 = "8" y1 = "23" x2 = "16" y2 = "23" />
</ svg >
</ button >
2026-01-19 00:38:11 +01:00
</ div >
2026-02-27 00:34:38 +01:00
< div class = "toolbar-right" >
2026-03-22 01:48:11 +01:00
<!-- Orchestrator button hidden until feature is ready -->
<!-- <button class="btn-toolbar btn-sm" onclick="app.toggleOrchestratorPanel()" title="Orchestrator Loop">⚙ Orchestrator</button> -->
2026-07-20 14:36:18 +02:00
< button class = "btn-toolbar btn-sm btn-cron btn-cron--hidden" onclick = "app.openCron()" title = "Cron Jobs" > ⏰ Cron</ button >
2026-02-27 00:34:38 +01:00
< span class = "version-display" id = "versionDisplay" title = "Codeman version" > v0.0.0</ span >
</ div >
2026-01-19 00:38:11 +01:00
</ footer >
2026-01-18 22:18:17 +01:00
<!-- Help Modal -->
< div class = "modal" id = "helpModal" >
< div class = "modal-backdrop" onclick = "app.closeHelp()" ></ div >
< div class = "modal-content" >
< div class = "modal-header" >
< h3 > Keyboard Shortcuts</ h3 >
2026-01-30 14:14:45 +01:00
< button class = "modal-close" onclick = "app.closeHelp()" aria-label = "Close help" > × </ button >
2026-01-18 22:18:17 +01:00
</ div >
< div class = "modal-body" >
2026-07-01 10:05:58 +00:00
< section class = "shortcut-section" >
< h4 > Session</ h4 >
< div class = "shortcuts-grid" >
< div >< kbd > Ctrl</ kbd > +< kbd > W</ kbd ></ div >< div > Close Session</ div >
2026-07-01 11:09:01 +00:00
< div >< kbd > Ctrl/Cmd/Option</ kbd > +< kbd > K</ kbd ></ div >< div > Find Open Session</ div >
2026-07-01 10:05:58 +00:00
< div >< kbd > Ctrl</ kbd > +< kbd > Tab</ kbd ></ div >< div > Next Session</ div >
< div >< kbd > Alt/Option</ kbd > +< kbd > [</ kbd > / < kbd > Alt/Option</ kbd > +< kbd > ]</ kbd ></ div >< div > Previous / Next Session</ div >
< div >< kbd > Alt/Option</ kbd > +< kbd > 1-9</ kbd ></ div >< div > Switch to Tab N</ div >
</ div >
</ section >
< section class = "shortcut-section" >
< h4 > Tabs</ h4 >
< div class = "shortcuts-grid" >
< div >< kbd > Ctrl</ kbd > +< kbd > {</ kbd ></ div >< div > Move Active Tab Left</ div >
< div >< kbd > Ctrl</ kbd > +< kbd > }</ kbd ></ div >< div > Move Active Tab Right</ div >
< div >< kbd > ArrowLeft</ kbd ></ div >< div > Focus Previous Tab</ div >
< div >< kbd > ArrowRight</ kbd ></ div >< div > Focus Next Tab</ div >
< div >< kbd > Home</ kbd ></ div >< div > Focus First Tab</ div >
< div >< kbd > End</ kbd ></ div >< div > Focus Last Tab</ div >
< div >< kbd > Enter</ kbd > / < kbd > Space</ kbd ></ div >< div > Activate Focused Tab</ div >
</ div >
</ section >
< section class = "shortcut-section" >
< h4 > Terminal</ h4 >
< div class = "shortcuts-grid" >
2026-08-05 02:38:57 +02:00
< div >< kbd > Ctrl</ kbd > +< kbd > C</ kbd ></ div >< div > Copy Selection (interrupts when nothing is selected)</ div >
< div >< kbd > Ctrl</ kbd > +< kbd > Shift</ kbd > +< kbd > C</ kbd ></ div >< div > Copy Selection</ div >
2026-07-01 10:05:58 +00:00
< div >< kbd > Ctrl</ kbd > +< kbd > L</ kbd ></ div >< div > Clear Terminal</ div >
< div >< kbd > Ctrl</ kbd > +< kbd > +</ kbd ></ div >< div > Increase Font</ div >
< div >< kbd > Ctrl</ kbd > +< kbd > -</ kbd ></ div >< div > Decrease Font</ div >
2026-07-12 19:06:12 +02:00
< div >< kbd > Ctrl</ kbd > +< kbd > Shift</ kbd > +< kbd > R</ kbd ></ div >< div > Restore Terminal Size</ div >
2026-07-01 10:05:58 +00:00
< div >< kbd > Shift</ kbd > +< kbd > Enter</ kbd ></ div >< div > Insert Newline</ div >
< div >< kbd > Ctrl</ kbd > +< kbd > Enter</ kbd ></ div >< div > Insert Newline</ div >
< div >< kbd > Ctrl</ kbd > +< kbd > Shift</ kbd > +< kbd > V</ kbd ></ div >< div > Voice Input</ div >
2026-07-12 20:03:53 +02:00
< div >< kbd > Shift</ kbd > +< kbd > Wheel</ kbd ></ div >< div > Scroll local history (when mouse passthrough is active)</ div >
2026-07-01 10:05:58 +00:00
</ div >
</ section >
< section class = "shortcut-section" >
< h4 > Panels</ h4 >
< div class = "shortcuts-grid" >
2026-07-01 21:19:20 +00:00
< div >< kbd > Ctrl</ kbd > +< kbd > ?</ kbd ></ div >< div > Show Shortcuts</ div >
< div >< kbd > Alt/Option</ kbd > +< kbd > ?</ kbd ></ div >< div > Show Shortcuts</ div >
2026-07-01 10:05:58 +00:00
< div >< kbd > Escape</ kbd ></ div >< div > Close Panels</ div >
</ div >
</ section >
2026-01-18 22:18:17 +01:00
</ div >
</ div >
</ div >
2026-01-19 00:38:11 +01:00
2026-07-27 17:06:36 +02:00
<!-- Web Tab (dashboard URL) editor -->
< div class = "modal" id = "webviewModal" >
< div class = "modal-backdrop" onclick = "app.closeWebviewModal()" ></ div >
< div class = "modal-content" >
< div class = "modal-header" >
< h3 id = "webviewModalTitle" > Add URL</ h3 >
< button class = "modal-close" onclick = "app.closeWebviewModal()" aria-label = "Close URL editor" > × </ button >
</ div >
< div class = "modal-body" >
< div class = "form-row" >
< label for = "webviewName" > Name</ label >
< input type = "text" id = "webviewName" placeholder = "Grafana" autocomplete = "off" spellcheck = "false" >
</ div >
< div class = "form-row" >
< label for = "webviewUrl" > URL</ label >
< input type = "text" id = "webviewUrl" placeholder = "http://100.70.56.18:4000/" autocomplete = "off"
autocapitalize = "off" spellcheck = "false" >
< span class = "form-hint" >
Reached from the Codeman server, so a tailnet or localhost address works even when
this browser cannot see it. Plain HTTP is fine: the dashboard is proxied through
Codeman, which is also what gets past dashboards that refuse to be embedded.
</ span >
</ div >
< div class = "form-row" >
< label for = "webviewIcon" > Icon</ label >
<!-- Click to pick; the field stays editable so any emoji still works. -->
< div class = "webview-icon-picker" id = "webviewIconPicker" role = "group" aria-label = "Choose an icon" ></ div >
< input type = "text" id = "webviewIcon" placeholder = "Or paste any emoji" maxlength = "8" autocomplete = "off" >
</ div >
< div class = "form-row" >
< label class = "checkbox-row" >< input type = "checkbox" id = "webviewSandboxed" checked > Open sandboxed</ label >
< span class = "form-hint" >
Recommended. A proxied dashboard is served from Codeman's own address, so unchecking
this lets its JavaScript read this page and call the API that starts agents. Uncheck
2026-08-09 04:06:23 +02:00
only for a dashboard you fully trust, or one whose own login needs cookies. Also
uncheck it if Codeman itself sits behind a cookie-authenticated reverse proxy
(e.g. Cloudflare Access): a sandboxed frame carries no auth cookie, so its asset
and API requests bounce to the login provider and the page loads broken.
2026-07-27 17:06:36 +02:00
</ span >
</ div >
< div class = "form-row" >
< button class = "btn-secondary" onclick = "app.testWebviewUrl()" > Test</ button >
< span class = "form-hint webview-probe-result" id = "webviewProbeResult" ></ span >
</ div >
</ div >
< div class = "form-actions webview-modal-actions" >
< button class = "btn-danger" id = "webviewDeleteBtn" onclick = "app.deleteWebview()" > Delete</ button >
< button class = "btn-secondary" onclick = "app.closeWebviewModal()" > Cancel</ button >
< button class = "btn-primary" onclick = "app.saveWebview()" > Save</ button >
</ div >
</ div >
</ div >
2026-06-29 11:35:56 +05:30
<!-- Cron Jobs Modal -->
< div class = "modal" id = "cronModal" >
< div class = "modal-backdrop" onclick = "app.closeCron()" ></ div >
2026-06-27 08:15:36 +05:30
< div class = "modal-content modal-lg" >
< div class = "modal-header" >
2026-06-29 11:35:56 +05:30
< h3 > Cron Jobs</ h3 >
< button class = "modal-close" onclick = "app.closeCron()" aria-label = "Close cron" > × </ button >
2026-06-27 08:15:36 +05:30
</ div >
< div class = "modal-body" >
2026-07-13 15:09:47 +02:00
< p class = "form-hint cron-modal-hint" > Times use the server's local timezone.</ p >
< div class = "cron-toolbar" >
2026-07-13 15:59:02 +02:00
< button class = "btn-toolbar btn-primary" onclick = "app.openCronJobForm()" > + New Job</ button >
< button class = "btn-toolbar" onclick = "app.refreshCron()" > Refresh</ button >
2026-06-27 08:15:36 +05:30
</ div >
<!-- Job list -->
2026-06-29 11:35:56 +05:30
< div id = "cronJobList" class = "cron-job-list" ></ div >
2026-06-27 08:15:36 +05:30
<!-- Create/Edit form (hidden until New/Edit) -->
2026-06-29 11:35:56 +05:30
< div id = "cronJobForm" class = "cron-job-form hidden" >
2026-07-13 15:09:47 +02:00
< div class = "cron-form-title" id = "cronFormTitle" > New Cron Job</ div >
2026-06-27 08:15:36 +05:30
< input type = "hidden" id = "schJobId" >
2026-07-13 15:09:47 +02:00
< div class = "form-section-header" > Basics</ div >
2026-06-27 08:15:36 +05:30
< div class = "form-row" >< label > Name</ label >< input type = "text" id = "schName" placeholder = "My nightly job" ></ div >
< div class = "form-row" >< label > Agent Type</ label >
2026-07-13 15:09:47 +02:00
< select id = "schAgentType" class = "form-select" onchange = "app.onCronAgentTypeChange()" >
2026-06-27 08:15:36 +05:30
< option value = "claude" > Claude</ option >
< option value = "shell" > Terminal / Shell</ option >
< option value = "opencode" > OpenCode</ option >
< option value = "codex" > Codex</ option >
< option value = "gemini" > Gemini</ option >
2026-08-04 12:59:40 +02:00
< option value = "antigravity" > Antigravity</ option >
2026-06-27 08:15:36 +05:30
</ select >
</ div >
< div class = "form-row" >< label > Working Directory</ label >< input type = "text" id = "schWorkingDir" placeholder = "/absolute/path" ></ div >
2026-07-12 19:25:42 +02:00
< div class = "form-row hidden" id = "schLaunchCommandRow" >< label > Launch Command</ label >< input type = "text" id = "schLaunchCommand" placeholder = "Optional — runs as the first command in the new shell" ></ div >
2026-07-13 15:09:47 +02:00
< div class = "form-section-header" > Prompt</ div >
2026-06-27 08:15:36 +05:30
< div class = "form-row" >< label > Prompt Source</ label >
2026-07-13 15:09:47 +02:00
< select id = "schPromptMode" class = "form-select" onchange = "app.onCronPromptModeChange()" >
2026-06-27 08:15:36 +05:30
< option value = "inline_text" > Inline text</ option >
< option value = "prompt_file_path" > Prompt file path</ option >
</ select >
</ div >
< div class = "form-row" id = "schPromptTextRow" >< label > Prompt</ label >< textarea id = "schPromptText" rows = "4" placeholder = "Prompt to send into the session" ></ textarea ></ div >
< div class = "form-row hidden" id = "schPromptFileRow" >< label > Prompt File Path</ label >< input type = "text" id = "schPromptFilePath" placeholder = "/absolute/path/to/prompt.md" ></ div >
< div class = "form-row" >< label > Input Mode</ label >
2026-07-13 15:09:47 +02:00
< select id = "schInputMode" class = "form-select" >
2026-06-27 08:15:36 +05:30
< option value = "typed" > Typed (via tmux)</ option >
< option value = "paste" > Paste (direct)</ option >
</ select >
</ div >
2026-07-13 15:09:47 +02:00
< div class = "form-section-header" > Schedule</ div >
2026-06-27 08:15:36 +05:30
< div class = "form-row" >< label > Schedule Type</ label >
2026-07-13 15:09:47 +02:00
< select id = "schScheduleType" class = "form-select" onchange = "app.onCronScheduleTypeChange()" >
2026-06-27 08:15:36 +05:30
< option value = "once" > Once</ option >
< option value = "interval" > Interval</ option >
< option value = "daily" > Daily</ option >
< option value = "weekly" > Weekly</ option >
</ select >
</ div >
< div class = "form-row" id = "schRunAtRow" >< label > Run At</ label >< input type = "datetime-local" id = "schRunAt" ></ div >
< div class = "form-row hidden" id = "schIntervalRow" >< label > Every (minutes)</ label >< input type = "number" id = "schIntervalMinutes" min = "1" value = "60" ></ div >
< div class = "form-row hidden" id = "schDailyRow" >< label > Daily Time (HH:MM)</ label >< input type = "time" id = "schDailyTime" ></ div >
< div class = "form-row hidden" id = "schWeeklyDaysRow" >< label > Weekdays</ label >
2026-06-29 11:35:56 +05:30
< span id = "schWeeklyDays" class = "cron-weekdays" >
2026-06-27 08:15:36 +05:30
< label >< input type = "checkbox" value = "0" > Sun</ label >
< label >< input type = "checkbox" value = "1" > Mon</ label >
< label >< input type = "checkbox" value = "2" > Tue</ label >
< label >< input type = "checkbox" value = "3" > Wed</ label >
< label >< input type = "checkbox" value = "4" > Thu</ label >
< label >< input type = "checkbox" value = "5" > Fri</ label >
< label >< input type = "checkbox" value = "6" > Sat</ label >
</ span >
</ div >
< div class = "form-row hidden" id = "schWeeklyTimeRow" >< label > Weekly Time (HH:MM)</ label >< input type = "time" id = "schWeeklyTime" ></ div >
2026-07-13 15:09:47 +02:00
< div class = "form-section-header" > Options</ div >
2026-06-27 08:15:36 +05:30
< div class = "form-row" >< label > On auto-run, if same agent running</ label >
2026-07-13 15:09:47 +02:00
< select id = "schConcurrencyPolicy" class = "form-select" >
2026-06-27 08:15:36 +05:30
< option value = "warn_only" > Run anyway</ option >
< option value = "skip_if_same_agent_running" > Skip this run</ option >
</ select >
</ div >
2026-07-13 15:09:47 +02:00
< div class = "form-row form-row-switch cron-switch-row" >
< div class = "cron-switch-text" >
< span class = "cron-switch-label" > Auto-close previous run's session</ span >
< span class = "cron-switch-desc" > Recurring schedules only: close the prior run's still-open session before the next run fires</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "schAutoClosePrev" checked >< span class = "slider" ></ span ></ label >
2026-07-12 19:25:42 +02:00
</ div >
2026-07-13 15:09:47 +02:00
< div class = "form-row form-row-switch cron-switch-row" >
< div class = "cron-switch-text" >
< span class = "cron-switch-label" > Enabled</ span >
< span class = "cron-switch-desc" > Off keeps the job saved but skips its schedule</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "schEnabled" checked >< span class = "slider" ></ span ></ label >
2026-06-27 08:15:36 +05:30
</ div >
< div class = "form-row" >< label > Notes</ label >< input type = "text" id = "schNotes" placeholder = "Optional" ></ div >
2026-07-13 15:09:47 +02:00
< div id = "cronFormError" class = "form-hint cron-form-error" ></ div >
< div class = "cron-form-actions" >
2026-07-13 15:59:02 +02:00
< button class = "btn-toolbar" onclick = "app.cancelCronJobForm()" > Cancel</ button >
< button class = "btn-toolbar btn-primary" onclick = "app.saveCronJob()" > Save</ button >
2026-06-27 08:15:36 +05:30
</ div >
2026-01-18 22:18:17 +01:00
</ div >
</ div >
</ div >
</ div >
2026-01-19 00:38:11 +01:00
2026-02-18 12:51:35 +01:00
<!-- Monitor Panel (Tmux Sessions + Background Tasks) -->
2026-01-19 02:48:28 +01:00
< div class = "monitor-panel" id = "monitorPanel" >
2026-01-19 10:36:30 +01:00
< div class = "monitor-panel-header" id = "monitorPanelHeader" >
2026-01-25 18:11:14 +01:00
< div class = "monitor-panel-title" > Monitor</ div >
2026-01-19 02:48:28 +01:00
< div class = "monitor-panel-actions" >
2026-03-01 16:27:40 +01:00
< button class = "btn-toolbar btn-sm btn-danger" onclick = "app.killAllMuxSessions()" title = "Kill all sessions and their tmux processes" > Kill All</ button >
2026-02-18 12:51:35 +01:00
< button class = "btn-icon-sm" onclick = "app.reconcileMuxSessions()" title = "Refresh tmux sessions" > ↻ </ button >
2026-01-19 19:28:46 +01:00
< button class = "btn-icon-sm" onclick = "app.toggleMonitorDetach()" title = "Detach panel" id = "monitorDetachBtn" > ⧉ </ button >
< button class = "btn-icon-sm" onclick = "app.toggleMonitorPanel()" title = "Toggle panel" id = "monitorToggleBtn" > ▲ </ button >
2026-01-20 17:54:59 +01:00
< button class = "btn-icon-sm" onclick = "app.closeMonitor()" title = "Close monitor" > × </ button >
2026-01-19 02:18:51 +01:00
</ div >
</ div >
2026-01-19 10:36:30 +01:00
< div class = "monitor-resize-handle" id = "monitorResizeHandle" ></ div >
2026-01-25 18:11:14 +01:00
< div class = "monitor-panel-body" id = "monitorTabContent" >
2026-01-19 02:48:28 +01:00
< div class = "monitor-section" >
< div class = "monitor-section-header" >
2026-02-18 12:51:35 +01:00
< span > Tmux Sessions</ span >
2026-01-19 02:48:28 +01:00
</ div >
2026-02-18 12:51:35 +01:00
< div class = "monitor-section-body" id = "muxSessionsBody" >
< div class = "monitor-empty" > No mux sessions</ div >
2026-01-19 02:48:28 +01:00
</ div >
</ div >
2026-01-29 11:27:44 +01:00
< div class = "monitor-section" id = "monitorPlanAgentsSection" style = "display: none;" >
< div class = "monitor-section-header" >
< span > Plan Wizard Agents</ span >
< span class = "task-stats" id = "monitorPlanAgentStats" > 0 active</ span >
< button class = "btn-toolbar btn-sm btn-danger" onclick = "app.cancelPlanFromMonitor()" title = "Cancel plan generation" > Cancel</ button >
</ div >
< div class = "monitor-section-body" id = "monitorPlanAgentsBody" >
< div class = "monitor-empty" > No plan agents</ div >
</ div >
</ div >
2026-01-28 07:07:18 +01:00
< div class = "monitor-section" id = "monitorSubagentsSection" >
< div class = "monitor-section-header" >
< span > Background Agents</ span >
< span class = "task-stats" id = "monitorSubagentStats" > 0 tracked</ span >
< button class = "btn-toolbar btn-sm btn-danger" onclick = "app.clearAllSubagents()" title = "Clear all" > Clear</ button >
</ div >
< div class = "monitor-section-body" id = "monitorSubagentsBody" >
< div class = "monitor-empty" > No background agents</ div >
</ div >
</ div >
2026-01-19 04:02:46 +01:00
< div class = "monitor-section" id = "backgroundTasksSection" style = "display: none;" >
2026-01-19 02:48:28 +01:00
< div class = "monitor-section-header" >
< span > Background Tasks</ span >
< span class = "task-stats" id = "taskPanelStats" > 0 running</ span >
</ div >
< div class = "monitor-section-body" id = "backgroundTasksBody" >
</ div >
</ div >
2026-01-25 17:40:28 +01:00
</ div >
2026-01-25 18:11:14 +01:00
</ div >
<!-- Subagents Panel (separate from Monitor) -->
< div class = "subagents-panel" id = "subagentsPanel" >
2026-01-25 18:33:43 +01:00
< div class = "subagents-panel-header" id = "subagentsPanelHeader" >
2026-01-25 18:11:14 +01:00
< div class = "subagents-panel-title" >
Subagents < span id = "subagentCountBadge" class = "subagent-badge" ></ span >
</ div >
< div class = "subagents-panel-actions" >
2026-01-28 07:07:18 +01:00
< button class = "btn-toolbar btn-sm btn-danger" onclick = "app.clearAllSubagents()" title = "Clear all tracked subagents" > Clear All</ button >
2026-01-25 18:33:43 +01:00
< button class = "btn-icon-sm" onclick = "app.toggleSubagentsDetach()" title = "Detach panel" id = "subagentsDetachBtn" > ⧉ </ button >
2026-01-25 18:11:14 +01:00
< button class = "btn-icon-sm" onclick = "app.toggleSubagentsPanel()" title = "Toggle panel" id = "subagentsToggleBtn" > ▲ </ button >
< button class = "btn-icon-sm" onclick = "app.closeSubagentsPanel()" title = "Close" > × </ button >
</ div >
</ div >
2026-01-25 18:33:43 +01:00
< div class = "subagents-resize-handle" id = "subagentsResizeHandle" ></ div >
2026-01-25 18:11:14 +01:00
< div class = "subagents-panel-body" id = "subagentsTabContent" >
2026-01-25 17:40:28 +01:00
< div class = "subagent-container" >
< div class = "subagent-list" id = "subagentList" >
< div class = "subagent-empty" > No background agents detected</ div >
2026-01-25 14:33:12 +01:00
</ div >
2026-01-25 17:40:28 +01:00
< div class = "subagent-detail" id = "subagentDetail" >
< div class = "subagent-empty" > Select an agent to view details</ div >
2026-01-25 14:33:12 +01:00
</ div >
</ div >
2026-01-19 00:38:11 +01:00
</ div >
</ div >
2026-06-15 08:40:05 +02:00
<!-- Ultracode / Workflow agents master-detail panel (opt-in via showUltracodeAgents) -->
< div class = "subagents-panel ultracode-agents-panel hidden" id = "ultracodeAgentsPanel" >
< div class = "subagents-panel-header" >
< div class = "subagents-panel-title" >
Ultracode Agents < span id = "ultracodeCountBadge" class = "subagent-badge" ></ span >
</ div >
< div class = "subagents-panel-actions" >
< button class = "btn-icon-sm" onclick = "app.toggleUltracodeAgentsPanel()" title = "Toggle panel" > ▲ </ button >
< button class = "btn-icon-sm" onclick = "app.closeUltracodeAgentsPanel()" title = "Close" > × </ button >
</ div >
</ div >
< div class = "subagents-panel-body" >
< div class = "subagent-container" >
< div class = "subagent-list ultracode-run-list" id = "ultracodeRunList" >
< div class = "subagent-empty" > No ultracode runs detected</ div >
</ div >
< div class = "subagent-detail ultracode-agent-grid" id = "ultracodeAgentGrid" >
< div class = "subagent-empty" > Select a run to view its agents</ div >
</ div >
</ div >
</ div >
</ div >
2026-01-19 03:40:57 +01:00
<!-- Session Options Modal (includes Respawn Settings) -->
2026-01-19 01:26:38 +01:00
< div class = "modal" id = "sessionOptionsModal" >
< div class = "modal-backdrop" onclick = "app.closeSessionOptions()" ></ div >
2026-01-19 03:40:57 +01:00
< div class = "modal-content" >
2026-01-19 01:26:38 +01:00
< div class = "modal-header" >
< h3 > Session Options</ h3 >
2026-01-30 14:14:45 +01:00
< button class = "modal-close" onclick = "app.closeSessionOptions()" aria-label = "Close session options" > × </ button >
2026-01-19 01:26:38 +01:00
</ div >
2026-01-19 18:25:31 +01:00
<!-- Modal Tabs -->
< div class = "modal-tabs" >
2026-02-26 10:16:20 +01:00
< button class = "modal-tab-btn active" data-tab = "respawn" data-claude-only onclick = "app.switchOptionsTab('respawn')" > Respawn</ button >
2026-02-26 15:58:26 +01:00
< button class = "modal-tab-btn" data-tab = "context" onclick = "app.switchOptionsTab('context')" > Context</ button >
2026-02-26 10:16:20 +01:00
< button class = "modal-tab-btn" data-tab = "ralph" data-claude-only onclick = "app.switchOptionsTab('ralph')" > Ralph / Todo</ button >
2026-01-26 09:06:49 +01:00
< button class = "modal-tab-btn" data-tab = "summary" onclick = "app.switchOptionsTab('summary')" > Summary</ button >
2026-01-19 18:25:31 +01:00
</ div >
2026-01-19 01:26:38 +01:00
< div class = "modal-body" >
2026-01-19 18:25:31 +01:00
<!-- Respawn Tab Content -->
< div class = "modal-tab-content" id = "respawn-tab" >
2026-01-19 03:40:57 +01:00
<!-- Respawn Settings Section -->
< div class = "session-respawn-section" id = "sessionRespawnSection" >
2026-06-10 20:41:34 +02:00
< div class = "auto-resume-box" >
< label class = "checkbox-inline" >
< input type = "checkbox" id = "modalAutoResumeEnabled" onchange = "app.autoSaveAutoResume()" >
< span > Auto-resume when usage limit resets</ span >
</ label >
< span class = "auto-resume-status" id = "autoResumeStatus" ></ span >
< span class = "form-hint" > If Claude pauses on a usage limit ("limit reached · resets 3pm"), Codeman waits for the reset time and automatically continues the work. Independent of the respawn loop below.</ span >
</ div >
2026-06-10 21:37:44 +02:00
< div class = "respawn-loop-box" >
< div class = "respawn-loop-title" > Respawn loop</ div >
< span class = "form-hint respawn-loop-hint" > One autonomous work cycle: whenever Claude goes idle, Codeman sends the update prompt, optionally runs /clear + /init, and kickstarts the next round — repeating for the chosen duration. All settings below belong to this loop; configure them, then press Enable.</ span >
2026-01-19 14:37:20 +01:00
< div class = "form-row" >
2026-01-30 11:30:51 +01:00
< label > Duration</ label >
2026-01-19 14:37:20 +01:00
< div class = "duration-presets" >
< button type = "button" class = "duration-preset-btn" data-minutes = "30" onclick = "app.selectDurationPreset('30')" > 30m</ button >
< button type = "button" class = "duration-preset-btn" data-minutes = "60" onclick = "app.selectDurationPreset('60')" > 1h</ button >
< button type = "button" class = "duration-preset-btn" data-minutes = "120" onclick = "app.selectDurationPreset('120')" > 2h</ button >
< button type = "button" class = "duration-preset-btn" data-minutes = "240" onclick = "app.selectDurationPreset('240')" > 4h</ button >
< button type = "button" class = "duration-preset-btn" data-minutes = "480" onclick = "app.selectDurationPreset('480')" > 8h</ button >
< button type = "button" class = "duration-preset-btn active" data-minutes = "" onclick = "app.selectDurationPreset('')" > ∞</ button >
< div class = "duration-custom" >
< button type = "button" class = "duration-preset-btn" data-minutes = "custom" onclick = "app.selectDurationPreset('custom')" > Custom</ button >
< div class = "input-suffix input-suffix-sm duration-custom-input" >
< input type = "number" id = "modalRespawnDuration" value = "" min = "1" placeholder = "min" >
< span > min</ span >
</ div >
</ div >
</ div >
</ div >
2026-01-30 14:14:45 +01:00
< div class = "form-section-header" > Presets</ div >
< div class = "form-row" >
< label > Load Preset</ label >
< div class = "preset-selector" >
< select id = "respawnPresetSelect" onchange = "app.updatePresetDescription()" >
< option value = "" > -- Select preset --</ option >
< optgroup label = "Built-in" id = "builtinPresetsGroup" >
</ optgroup >
< optgroup label = "Custom" id = "customPresetsGroup" >
</ optgroup >
</ select >
< button type = "button" class = "btn btn-sm" onclick = "app.loadRespawnPreset()" title = "Apply preset" > Apply</ button >
< button type = "button" class = "btn btn-sm btn-secondary" onclick = "app.saveCurrentAsPreset()" title = "Save current config as preset" > Save As...</ button >
</ div >
< p class = "form-hint" id = "presetDescriptionHint" ></ p >
</ div >
2026-06-10 21:37:44 +02:00
< div class = "form-section-header" > Cycle Steps</ div >
2026-01-19 03:40:57 +01:00
< div class = "form-row" >
2026-01-30 11:39:15 +01:00
< label > 1. Update Prompt</ label >
2026-06-10 20:41:34 +02:00
< textarea id = "modalRespawnPrompt" rows = "1" placeholder = "Prompt to send when idle" onchange = "app.autoSaveRespawnConfig()" style = "resize: vertical; min-height: 30px;" > update all the docs and CLAUDE.md</ textarea >
2026-01-19 03:40:57 +01:00
</ div >
2026-01-30 11:39:15 +01:00
< div class = "respawn-options-row" style = "margin: 8px 0;" >
< label class = "checkbox-inline" >
< input type = "checkbox" id = "modalRespawnSendClear" checked onchange = "app.autoSaveRespawnConfig()" >
< span > 2. Send /clear</ span >
</ label >
< label class = "checkbox-inline" >
< input type = "checkbox" id = "modalRespawnSendInit" checked onchange = "app.autoSaveRespawnConfig()" >
< span > 3. Send /init</ span >
</ label >
2026-06-10 20:41:34 +02:00
< label class = "checkbox-inline" title = "Presses Enter for plan approvals and default question options" >
2026-01-30 11:30:51 +01:00
< input type = "checkbox" id = "modalRespawnAutoAccept" checked onchange = "app.autoSaveRespawnConfig()" >
< span > Auto-accept prompts</ span >
</ label >
2026-01-24 19:12:47 +01:00
</ div >
2026-06-10 20:41:34 +02:00
< div class = "form-row" >
< label > 4. Kickstart Prompt</ label >
< textarea id = "modalRespawnKickstart" rows = "1" placeholder = "Optional: prompt if /init doesn't trigger work" onchange = "app.autoSaveRespawnConfig()" style = "resize: vertical; min-height: 30px;" ></ textarea >
< span class = "form-hint" > Sent only when /init completes but Claude stays idle · Auto-accept presses Enter for plan approvals and default options</ span >
</ div >
2026-06-10 21:37:44 +02:00
< div class = "respawn-header" >
< div class = "session-respawn-status" id = "sessionRespawnStatus" >
< span class = "respawn-status-indicator" ></ span >
< span class = "respawn-status-text" > Not active</ span >
</ div >
< div class = "respawn-actions" >
< button class = "btn-toolbar btn-success btn-sm" onclick = "app.enableRespawnFromModal()" id = "modalEnableRespawnBtn" > Enable</ button >
< button class = "btn-toolbar btn-danger btn-sm" onclick = "app.stopRespawnFromModal()" id = "modalStopRespawnBtn" style = "display: none;" > Stop</ button >
</ div >
</ div >
</ div > <!-- End respawn-loop-box -->
2026-01-24 19:12:47 +01:00
</ div >
</ div > <!-- End respawn-tab -->
<!-- Context Management Tab Content -->
< div class = "modal-tab-content hidden" id = "context-tab" >
2026-01-30 14:14:45 +01:00
< div class = "form-section-header" > Appearance</ div >
2026-02-18 07:28:15 +01:00
< div class = "form-row" >
< label > Session Name</ label >
2026-04-11 06:59:50 +02:00
< div style = "display: flex; align-items: center; gap: 4px;" >
< span id = "modalSessionPrefix" style = "color: var(--text-muted); white-space: nowrap; display: none;" ></ span >
< input type = "text" id = "modalSessionName" maxlength = "128" placeholder = "Auto (directory name)" onblur = "app.saveSessionName()" style = "flex: 1;" >
</ div >
2026-02-18 07:28:15 +01:00
< span class = "form-hint" > Custom name shown in the tab (right-click tab to rename inline)</ span >
</ div >
2026-01-30 14:14:45 +01:00
< div class = "form-row" >
< label > Session Color</ label >
< div class = "color-picker" id = "sessionColorPicker" >
< button type = "button" class = "color-swatch" data-color = "default" title = "Default" >
< span class = "swatch-inner" style = "background: var(--bg-hover)" ></ span >
</ button >
< button type = "button" class = "color-swatch" data-color = "red" title = "Red" >
< span class = "swatch-inner" style = "background: var(--session-red)" ></ span >
</ button >
< button type = "button" class = "color-swatch" data-color = "orange" title = "Orange" >
< span class = "swatch-inner" style = "background: var(--session-orange)" ></ span >
</ button >
< button type = "button" class = "color-swatch" data-color = "yellow" title = "Yellow" >
< span class = "swatch-inner" style = "background: var(--session-yellow)" ></ span >
</ button >
< button type = "button" class = "color-swatch" data-color = "green" title = "Green" >
< span class = "swatch-inner" style = "background: var(--session-green)" ></ span >
</ button >
< button type = "button" class = "color-swatch" data-color = "blue" title = "Blue" >
< span class = "swatch-inner" style = "background: var(--session-blue)" ></ span >
</ button >
< button type = "button" class = "color-swatch" data-color = "purple" title = "Purple" >
< span class = "swatch-inner" style = "background: var(--session-purple)" ></ span >
</ button >
< button type = "button" class = "color-swatch" data-color = "pink" title = "Pink" >
< span class = "swatch-inner" style = "background: var(--session-pink)" ></ span >
</ button >
</ div >
</ div >
2026-01-30 11:30:51 +01:00
< div class = "form-section-header" > Token Management</ div >
< div class = "context-settings-grid" >
< div class = "context-setting" >
< div class = "context-setting-header" >
< label class = "checkbox-inline" >
< input type = "checkbox" id = "modalAutoCompactEnabled" onchange = "app.autoSaveAutoCompact()" >
< span > Auto-Compact</ span >
</ label >
< div class = "input-suffix input-suffix-sm" >
< input type = "number" id = "modalAutoCompactThreshold" value = "110000" min = "10000" max = "500000" step = "10000" onchange = "app.autoSaveAutoCompact()" >
< span > tokens</ span >
</ div >
2026-01-19 13:08:53 +01:00
</ div >
2026-01-30 11:30:51 +01:00
< input type = "text" id = "modalAutoCompactPrompt" value = "" placeholder = "Focus prompt (optional)" onchange = "app.autoSaveAutoCompact()" >
< span class = "form-hint" > Summarizes context while preserving key information</ span >
2026-01-19 13:08:53 +01:00
</ div >
2026-01-30 11:30:51 +01:00
< div class = "context-setting" >
< div class = "context-setting-header" >
< label class = "checkbox-inline" >
< input type = "checkbox" id = "modalAutoClearEnabled" onchange = "app.autoSaveAutoClear()" >
< span > Auto-Clear</ span >
</ label >
< div class = "input-suffix input-suffix-sm" >
< input type = "number" id = "modalAutoClearThreshold" value = "140000" min = "10000" max = "500000" step = "10000" onchange = "app.autoSaveAutoClear()" >
< span > tokens</ span >
</ div >
2026-01-19 03:40:57 +01:00
</ div >
2026-01-30 11:30:51 +01:00
< span class = "form-hint" > Full context reset at threshold (use higher than compact)</ span >
2026-01-19 03:40:57 +01:00
</ div >
</ div >
2026-01-28 07:07:18 +01:00
2026-01-30 11:30:51 +01:00
< div class = "form-section-header" > File Watching</ div >
2026-01-28 07:07:18 +01:00
< div class = "form-row form-row-switch" >
< label > Image Watcher</ label >
< label class = "switch" >
< input type = "checkbox" id = "modalImageWatcherEnabled" checked onchange = "app.toggleSessionImageWatcher()" >
< span class = "slider" ></ span >
</ label >
2026-01-30 11:30:51 +01:00
< span class = "form-hint" > Auto-popup new images (screenshots, generated files) in this session's directory</ span >
2026-01-28 07:07:18 +01:00
</ div >
2026-01-30 17:50:02 +01:00
< div class = "form-section-header" > Performance</ div >
< div class = "form-row form-row-switch" >
< label > Flicker Filter</ label >
< label class = "switch" >
< input type = "checkbox" id = "modalFlickerFilterEnabled" onchange = "app.toggleFlickerFilter()" >
< span class = "slider" ></ span >
</ label >
< span class = "form-hint" > Buffers output after screen clears to reduce Ink UI flickering (adds ~50ms latency)</ span >
</ div >
2026-01-24 19:12:47 +01:00
</ div > <!-- End context-tab -->
2026-01-19 18:25:31 +01:00
<!-- Ralph Wiggum Tab Content -->
< div class = "modal-tab-content hidden" id = "ralph-tab" >
2026-01-19 18:56:34 +01:00
< div class = "form-row form-row-switch" >
< label > Enable Tracker</ label >
< label class = "switch" >
< input type = "checkbox" id = "modalRalphEnabled" >
< span class = "slider" ></ span >
</ label >
2026-01-30 11:30:51 +01:00
< span class = "form-hint" > Auto-detects loops and todos in Claude output</ span >
2026-01-19 18:56:34 +01:00
</ div >
2026-01-19 18:25:31 +01:00
< div class = "form-row" >
< label > Completion Phrase</ label >
< input type = "text" id = "modalRalphPhrase" placeholder = "e.g., COMPLETE, DONE, FINISHED" >
2026-01-30 11:30:51 +01:00
< span class = "form-hint" > Phrase Claude outputs when loop is complete (without < promise> tags)</ span >
2026-01-19 18:25:31 +01:00
</ div >
2026-01-30 11:30:51 +01:00
< div class = "form-section-header" > Limits</ div >
< div class = "ralph-limits-grid" >
< div class = "form-col" >
< label > Max Iterations</ label >
< input type = "number" id = "modalRalphMaxIterations" value = "0" min = "0" max = "1000" >
< span class = "form-hint" > 0 = unlimited</ span >
</ div >
< div class = "form-col" >
< label > Max Todos</ label >
< input type = "number" id = "modalRalphMaxTodos" value = "50" min = "1" max = "500" >
< span class = "form-hint" > Per session</ span >
</ div >
< div class = "form-col" >
< label > Todo Expiration</ label >
< div class = "input-suffix input-suffix-sm" >
< input type = "number" id = "modalRalphTodoExpiration" value = "60" min = "1" max = "1440" >
< span > min</ span >
</ div >
2026-01-19 18:25:31 +01:00
</ div >
</ div >
< div class = "ralph-config-actions" >
< button class = "btn-toolbar btn-primary" onclick = "app.saveRalphConfig()" > Save Ralph Config</ button >
</ div >
</ div > <!-- End ralph-tab -->
2026-01-26 09:06:49 +01:00
<!-- Summary Tab Content -->
< div class = "modal-tab-content hidden" id = "summary-tab" >
<!-- Filter Buttons -->
< div class = "run-summary-filters" >
< button class = "filter-btn active" data-filter = "all" onclick = "app.filterRunSummary('all')" > All</ button >
< button class = "filter-btn" data-filter = "errors" onclick = "app.filterRunSummary('errors')" > Errors</ button >
< button class = "filter-btn" data-filter = "warnings" onclick = "app.filterRunSummary('warnings')" > Warnings</ button >
< button class = "filter-btn" data-filter = "respawn" onclick = "app.filterRunSummary('respawn')" > Respawn</ button >
< button class = "filter-btn" data-filter = "idle" onclick = "app.filterRunSummary('idle')" > Idle/Working</ button >
</ div >
<!-- Timeline -->
< div class = "run-summary-timeline" id = "runSummaryTimeline" >
< p class = "empty-message" > Select a session to view summary</ p >
</ div >
<!-- Footer with actions -->
< div class = "run-summary-footer" >
< span class = "run-summary-session-info" id = "runSummarySessionInfo" ></ span >
< div class = "run-summary-actions" >
< label class = "auto-refresh-label" >
< input type = "checkbox" id = "runSummaryAutoRefresh" onchange = "app.toggleRunSummaryAutoRefresh()" >
Auto-refresh
</ label >
< button class = "btn-toolbar btn-sm" onclick = "app.refreshRunSummary()" title = "Refresh summary" > ↻ Refresh</ button >
< button class = "btn-toolbar btn-sm" onclick = "app.exportRunSummary('json')" title = "Export as JSON" > Export JSON</ button >
< button class = "btn-toolbar btn-sm" onclick = "app.exportRunSummary('md')" title = "Export as Markdown" > Export MD</ button >
</ div >
</ div >
</ div > <!-- End summary-tab -->
2026-01-19 01:26:38 +01:00
</ div >
</ div >
</ div >
2026-01-19 02:10:44 +01:00
<!-- Close Session Confirmation Modal -->
< div class = "modal" id = "closeConfirmModal" >
< div class = "modal-backdrop" onclick = "app.cancelCloseSession()" ></ div >
< div class = "modal-content modal-sm" >
< div class = "modal-header" >
2026-01-19 04:55:51 +01:00
< h3 > Close Session</ h3 >
2026-01-30 14:14:45 +01:00
< button class = "modal-close" onclick = "app.cancelCloseSession()" aria-label = "Cancel close session" > × </ button >
2026-01-19 02:10:44 +01:00
</ div >
< div class = "modal-body" >
< p class = "modal-session-name" id = "closeConfirmSessionName" ></ p >
</ div >
2026-01-19 04:55:51 +01:00
< div class = "close-options" >
< button class = "close-option" onclick = "app.confirmCloseSession(false)" >
< span class = "close-option-title" > Remove Tab</ span >
2026-02-26 15:06:36 +01:00
< span class = "close-option-desc" > Tmux session keeps running in background</ span >
2026-01-19 04:55:51 +01:00
</ button >
< button class = "close-option close-option-danger" onclick = "app.confirmCloseSession(true)" >
2026-02-26 15:06:36 +01:00
< span class = "close-option-title" id = "closeConfirmKillTitle" > Kill Tmux & Claude Code</ span >
2026-01-19 04:55:51 +01:00
< span class = "close-option-desc" > Terminate the session completely</ span >
</ button >
</ div >
< div class = "modal-footer-cancel" >
< button class = "btn-toolbar btn-sm" onclick = "app.cancelCloseSession()" > Cancel</ button >
2026-01-19 02:10:44 +01:00
</ div >
</ div >
</ div >
2026-01-19 02:08:58 +01:00
<!-- App Settings Modal -->
< div class = "modal" id = "appSettingsModal" >
< div class = "modal-backdrop" onclick = "app.closeAppSettings()" ></ div >
2026-08-10 03:59:43 +02:00
< div class = "modal-content modal-lg set-shell" >
< div class = "modal-header set-shell-head" >
< h3 > Settings</ h3 >
<!-- Close first in the DOM so the focus trap still lands on it, not on
Save; row-reverse keeps Save to the left of it on phones. -->
< div class = "set-head-actions" >
< button class = "modal-close" onclick = "app.closeAppSettings()" aria-label = "Close app settings" > × </ button >
< button class = "set-head-save" onclick = "app.saveAppSettings()" > Save</ button >
2026-07-09 11:26:24 -04:00
</ div >
2026-01-19 02:08:58 +01:00
</ div >
2026-08-10 03:59:43 +02:00
< div class = "set-body" >
<!-- Left rail: a table of contents over the single scrolling document on
the right. On phones it collapses and #appSettingsJump takes over. -->
< nav class = "set-rail" id = "appSettingsRail" aria-label = "Settings sections" >
< div class = "set-search" >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2.2" aria-hidden = "true" >< circle cx = "11" cy = "11" r = "7" />< path d = "M21 21l-4.3-4.3" /></ svg >
< input type = "search" id = "appSettingsSearch" placeholder = "Search settings" autocomplete = "off" aria-label = "Search settings" >
</ div >
< div class = "set-rail-items" id = "appSettingsRailItems" >
< button type = "button" class = "set-rail-item active" data-section = "settings-terminal" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M4 17l6-6-6-6" />< path d = "M12 19h8" /></ svg >
< span > Terminal & Input</ span >
</ button >
< button type = "button" class = "set-rail-item" data-section = "settings-appearance" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< circle cx = "12" cy = "12" r = "9" />< path d = "M12 3a9 9 0 0 0 0 18 4.5 4.5 0 0 0 0-9 4.5 4.5 0 0 1 0-9z" /></ svg >
< span > Appearance</ span >
</ button >
< button type = "button" class = "set-rail-item" data-section = "settings-layout" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< rect x = "3" y = "3" width = "18" height = "18" rx = "2" />< path d = "M3 9h18M9 21V9" /></ svg >
< span > Header & Panels</ span >
</ button >
< button type = "button" class = "set-rail-item" data-section = "settings-models" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z" />< path d = "M12 12l8-4.5M12 12v9M12 12L4 7.5" /></ svg >
< span > Models</ span >
</ button >
< button type = "button" class = "set-rail-item" data-section = "settings-clis" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M8 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2" />< path d = "M16 3h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-2" />< circle cx = "12" cy = "12" r = "2.5" /></ svg >
< span > Agents & CLIs</ span >
</ button >
< button type = "button" class = "set-rail-item" data-section = "settings-notifications" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9" />< path d = "M13.7 21a2 2 0 0 1-3.4 0" /></ svg >
< span > Notifications</ span >
</ button >
< button type = "button" class = "set-rail-item" data-section = "settings-voice" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< rect x = "9" y = "2" width = "6" height = "12" rx = "3" />< path d = "M5 11a7 7 0 0 0 14 0" />< path d = "M12 18v4" /></ svg >
< span > Voice</ span >
</ button >
< button type = "button" class = "set-rail-item" data-section = "settings-shortcuts" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< rect x = "2" y = "5" width = "20" height = "14" rx = "2" />< path d = "M6 9h.01M10 9h.01M14 9h.01M18 9h.01M6 13h.01M18 13h.01M9 13h6" /></ svg >
< span > Shortcuts</ span >
</ button >
< button type = "button" class = "set-rail-item" data-section = "settings-system" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< circle cx = "12" cy = "12" r = "3" />< path d = "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9c.26.6.86 1 1.51 1H21a2 2 0 0 1 0 4h-.09c-.65 0-1.25.4-1.51 1z" /></ svg >
< span > System</ span >
</ button >
</ div >
< p class = "set-rail-foot" > Settings marked < b > device</ b > stay on this browser. The rest sync to every device.</ p >
</ nav >
< div class = "set-doc" id = "appSettingsDoc" >
<!-- Phone only: names the section you are in and opens the jump list -->
< button type = "button" class = "set-jump" id = "appSettingsJump" aria-haspopup = "menu" aria-expanded = "false" >
< svg class = "set-jump-ico" width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M4 17l6-6-6-6" />< path d = "M12 19h8" /></ svg >
< span class = "set-jump-label" > Terminal & Input</ span >
< svg class = "set-jump-chev" width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2.3" stroke-linecap = "round" aria-hidden = "true" >< path d = "M6 9l6 6 6-6" /></ svg >
</ button >
< div class = "set-jump-veil" id = "appSettingsJumpVeil" ></ div >
< div class = "set-jump-menu" id = "appSettingsJumpMenu" role = "menu" ></ div >
< p class = "set-empty" id = "appSettingsSearchEmpty" hidden > No settings match that search.</ p >
<!-- ══ Terminal & Input ══════════════════════════════════════ -->
< section class = "set-section" id = "settings-terminal" data-label = "Terminal & Input" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M4 17l6-6-6-6" />< path d = "M12 19h8" /></ svg >
< h2 > Terminal & Input</ h2 >
</ div >
< p class = "set-section-blurb" > How keystrokes reach the agent, and how the terminal draws.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Typing</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "local echo typing latency mobile" >
< div class = "set-row-text" >
< span class = "set-row-label" > Local Echo</ span >
< span class = "set-row-desc" > Paint keystrokes instantly instead of waiting for the round trip. Recommended on phones and high-latency links.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsLocalEcho" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "cjk input ime chinese japanese korean" >
< div class = "set-row-text" >
< span class = "set-row-label" > CJK Input</ span >
< span class = "set-row-desc" > Dedicated IME field for Chinese, Japanese and Korean composition.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsCjkInput" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "extended keyboard bar tab esc arrows phone" >
< div class = "set-row-text" >
< span class = "set-row-label" > Extended Keyboard Bar < span class = "set-tag" > phone</ span ></ span >
< span class = "set-row-desc" > Extra phone keys: Tab, Shift+Tab, Esc, arrows, Ctrl+O.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsExtendedKeyboardBar" >< span class = "slider" ></ span ></ label >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Scrolling & rendering</ h4 ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "wheel scroll local history scrollback transcript" >
< div class = "set-row-text" >
< span class = "set-row-label" > Wheel Scrolls Local History < span class = "set-scope" > device</ span ></ span >
< span class = "set-row-desc" > Plain wheel or two-finger swipe pages the terminal's own scrollback instead of the CLI transcript. Shift+wheel always reaches local scrollback.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsTerminalWheelLocal" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" id = "appSettingsWebglRendererItem" data-search = "webgl renderer gpu terminal" >
< div class = "set-row-text" >
< span class = "set-row-label" > WebGL Renderer < span class = "set-tag" > desktop</ span > < span class = "set-scope" > device</ span ></ span >
< span class = "set-row-desc" > GPU-accelerated terminal drawing. Codeman falls back to the DOM renderer after repeated GPU stalls.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsWebglRenderer" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" id = "appSettingsGestureControlItem" data-search = "gesture control camera hand tracking" >
< div class = "set-row-text" >
< span class = "set-row-label" > Gesture Control < span class = "set-tag set-tag-beta" > beta</ span > < span class = "set-scope" > synced</ span ></ span >
< span class = "set-row-desc" > Camera hand-tracking overlay. Applied on reload; the instance must run with CODEMAN_GESTURE=1.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsGestureControl" >< span class = "slider" ></ span ></ label >
</ div >
</ div >
</ div >
</ section >
<!-- ══ Appearance ═══════════════════════════════════════════════ -->
< section class = "set-section" id = "settings-appearance" data-label = "Appearance" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< circle cx = "12" cy = "12" r = "9" />< path d = "M12 3a9 9 0 0 0 0 18 4.5 4.5 0 0 0 0-9 4.5 4.5 0 0 1 0-9z" /></ svg >
< h2 > Appearance</ h2 >
</ div >
< p class = "set-section-blurb" > Theme, motion, and what this install calls itself.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Theme</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row has-field" data-search = "skin theme palette colors dark light" >
< div class = "set-row-text" >
< span class = "set-row-label" > Skin</ span >
< span class = "set-row-desc" > Palette for the terminal and the UI.</ span >
</ div >
< select id = "appSettingsSkin" class = "set-select" >
< optgroup label = "Light" >
< option value = "paper-gray" > Paper Gray</ option >
< option value = "solarized-light" > Solarized Light</ option >
< option value = "catppuccin-latte" > Catppuccin Latte</ option >
< option value = "rose-pine-dawn" > Rosé Pine Dawn</ option >
</ optgroup >
< optgroup label = "Dark" >
< option value = "daylight-blue" > Daylight Blue</ option >
< option value = "daylight-green" > Daylight Green</ option >
< option value = "og" > OG Codeman</ option >
</ optgroup >
</ select >
</ div >
< div class = "set-row has-field" data-search = "entrance animations motion tabs windows" >
< div class = "set-row-text" >
< span class = "set-row-label" > Entrance Animations</ span >
< span class = "set-row-desc" > How new tabs, panes and agent windows arrive. Add ?animlab=1 to the URL for per-surface control.</ span >
</ div >
< select id = "appSettingsEntranceAnim" class = "set-select" >
< option value = "legacy" > Off (default)</ option >
< option value = "terminal" > Terminal (CRT)</ option >
< option value = "beamdown" > Beam down</ option >
< option value = "quiet" > Quiet</ option >
< option value = "playful" > Playful</ option >
< option value = "custom" > Custom (set in the lab)</ option >
</ select >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Identity</ h4 ></ div >
< div class = "set-group-body" >
< div class = "set-row has-field" data-search = "display name branding title" >
< div class = "set-row-text" >
< span class = "set-row-label" > Display Name < span class = "set-scope" > synced</ span ></ span >
< span class = "set-row-desc" > Shown in the browser UI and the window title. Unicode is fine.</ span >
</ div >
< input type = "text" id = "appSettingsDisplayName" class = "set-input" maxlength = "40" placeholder = "Codeman" autocomplete = "off" >
</ div >
< div class = "set-row has-field" data-search = "language interface chinese english" >
< div class = "set-row-text" >
< span class = "set-row-label" > Interface Language < span class = "set-scope" > device</ span ></ span >
< span class = "set-row-desc" > Dialogs and status messages use the same language.</ span >
</ div >
< select id = "appSettingsLanguage" class = "set-select" >
< option value = "en" > English</ option >
< option value = "zh-CN" > 简体中文</ option >
</ select >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Tabs</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "tall tabs folder name two rows" >
< div class = "set-row-text" >
< span class = "set-row-label" > Tall Tabs</ span >
< span class = "set-row-desc" > Show the folder under the session name and let the tab bar wrap.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsTabTwoRows" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "pop out detach tab window" >
< div class = "set-row-text" >
< span class = "set-row-label" > Pop-out Button on Tabs</ span >
< span class = "set-row-desc" > Open a session in its own window from the tab.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsShowTabDetachButton" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" id = "appSettingsMobileOverviewItem" data-search = "overview home screen phone logo" >
< div class = "set-row-text" >
< span class = "set-row-label" > Overview Home Screen < span class = "set-tag" > phone</ span ></ span >
< span class = "set-row-desc" > The C logo opens the session overview instead of the welcome screen.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsMobileOverview" checked >< span class = "slider" ></ span ></ label >
</ div >
</ div >
</ div >
</ section >
<!-- ══ Header & Panels ═══════════════════════════════════════ -->
< section class = "set-section" id = "settings-layout" data-label = "Header & Panels" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< rect x = "3" y = "3" width = "18" height = "18" rx = "2" />< path d = "M3 9h18M9 21V9" /></ svg >
< h2 > Header & Panels</ h2 >
</ div >
< p class = "set-section-blurb" > Pick the buttons and panels you actually use. Everything else stays out of the way.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Header buttons</ h4 >< span class = "set-scope" > device</ span ></ div >
< p class = "set-group-hint" > Tap to show a control in the header. Multi-monitor is the one entry here that syncs across devices.</ p >
< div class = "set-group-body" >
< div class = "set-chips" data-search = "header buttons plan usage font stats lifecycle response file viewer attachments monitor session away cron redraw" >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowPlanUsageLimits" >< span > Plan Usage</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowFontControls" >< span > Font Size</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowSystemStats" checked >< span > System Stats</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowLifecycleLog" >< span > Lifecycle Log</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowResponseViewer" >< span > Response Viewer</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowFileViewerButton" >< span > File Viewer</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowAttachmentsButton" >< span > Attachments</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowMultiMonitorButton" >< span > Multi-monitor</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowSessionButton" >< span > Session Manager</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowAwayDigestButton" >< span > Away Digest</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowCronButton" >< span > Cron</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowRedrawButton" >< span > Redraw Terminal</ span ></ label >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Panels</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "set-chips" data-search = "panels monitor project insights file browser subagents" >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowMonitor" checked >< span > Monitor</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowProjectInsights" >< span > Project Insights</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowFileBrowser" >< span > File Browser</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowSubagents" >< span > Subagents</ span ></ label >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Cross-session features</ h4 >< span class = "set-scope" > synced</ span ></ div >
< p class = "set-group-hint" > Approvals answers prompts from any session in one place. Read My Mind captures your prompts and predicts the next one on demand; both cost tokens and never auto-send.</ p >
< div class = "set-group-body" >
< div class = "set-chips" data-search = "approvals inbox read my mind ultracode agents floating windows workflow" >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsApprovalsInbox" >< span > Approvals Inbox</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsReadMyMind" >< span > Read My Mind</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsShowUltracodeAgents" >< span > Ultracode Agents</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsUltracodeFloatingWindows" >< span > Ultracode Windows</ span ></ label >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Subagent windows</ h4 ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "subagent tracking background agents" >
< div class = "set-row-text" >
< span class = "set-row-label" > Track background agents < span class = "set-scope" > synced</ span ></ span >
< span class = "set-row-desc" > Follow Claude Code subagents in real time.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsSubagentTracking" checked >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "subagent active tab only windows" >
< div class = "set-row-text" >
< span class = "set-row-label" > Active tab only < span class = "set-scope" > device</ span ></ span >
< span class = "set-row-desc" > Only show agent windows for the selected session.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsSubagentActiveTabOnly" checked >< span class = "slider" ></ span ></ label >
</ div >
</ div >
</ div >
</ section >
<!-- ══ Models ═══════════════════════════════════════════════════ -->
< section class = "set-section" id = "settings-models" data-label = "Models" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z" />< path d = "M12 12l8-4.5M12 12v9M12 12L4 7.5" /></ svg >
< h2 > Models</ h2 >
</ div >
< p class = "set-section-blurb" > One place for what new sessions run on, and what background tasks are routed to.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > New Claude sessions</ h4 >< span class = "set-scope" > synced</ span ></ div >
< p class = "set-group-hint" > A soft default, pinned via the case's .claude/settings.local.json. /model still switches inside a running session.</ p >
< div class = "set-group-body" >
<!-- Cards are built from the select below by settings-ui.js, which keeps
the select as the single source of truth for load/save. -->
< div class = "set-modelgrid" id = "appSettingsModelCards" role = "radiogroup" aria-label = "Model for new Claude sessions" data-search = "model opus sonnet haiku fable claude" ></ div >
< select id = "appSettingsClaudeModel" class = "set-select set-field-hidden" aria-hidden = "true" tabindex = "-1" >
< option value = "" data-meta = "Whatever the CLI picks" > Default (CLI setting)</ option >
< option value = "claude-fable-5" data-meta = "Most powerful" data-base = "claude-fable-5" data-ctx = "1" > Fable 5</ option >
< option value = "claude-fable-5[1m]" data-variant = "1m" data-base = "claude-fable-5" > Fable 5 (1M context)</ option >
< option value = "opus" data-meta = "Most capable" data-base = "opus" data-ctx = "1" > Opus</ option >
< option value = "opus[1m]" data-variant = "1m" data-base = "opus" > Opus (1M context)</ option >
< option value = "claude-opus-4-6" data-meta = "Previous generation" data-base = "claude-opus-4-6" data-ctx = "1" > Opus 4.6</ option >
< option value = "claude-opus-4-6[1m]" data-variant = "1m" data-base = "claude-opus-4-6" > Opus 4.6 (1M context)</ option >
< option value = "sonnet" data-meta = "Balanced" data-base = "sonnet" > Sonnet</ option >
< option value = "haiku" data-meta = "Fast & cheap" data-base = "haiku" > Haiku</ option >
</ select >
< div class = "set-row" id = "appSettingsContextRow" data-search = "1m context window opus long" >
< div class = "set-row-text" >
< span class = "set-row-label" > 1M context window</ span >
< span class = "set-row-desc" id = "appSettingsContextDesc" > Available for Fable 5, Opus and Opus 4.6.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsOpusContext1m" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row set-row-block" data-search = "thinking effort ultracode xhigh max reasoning" >
< div class = "set-row-text" >
< span class = "set-row-label" > Thinking effort</ span >
< span class = "set-row-desc" > Switchable in-session with /effort. Ultracode runs multi-agent workflows.</ span >
</ div >
< div class = "set-segment" id = "appSettingsEffortSegment" role = "radiogroup" aria-label = "Thinking effort" ></ div >
< select id = "appSettingsThinkingEffort" class = "set-select set-field-hidden" aria-hidden = "true" tabindex = "-1" >
< option value = "" > Default</ option >
< option value = "low" > Low</ option >
< option value = "medium" > Medium</ option >
< option value = "high" > High</ option >
< option value = "xhigh" > XHigh</ option >
< option value = "max" > Max</ option >
< option value = "ultracode" > Ultracode</ option >
</ select >
</ div >
</ div >
</ div >
< div class = "set-group set-group-advanced" id = "appSettingsTaskRouting" >
< div class = "set-group-head set-group-head-toggle" role = "button" tabindex = "0" aria-expanded = "false" >
< h4 > Task routing</ h4 >
< span class = "set-scope" > synced</ span >
< span class = "set-adv" > Advanced
< svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2.4" stroke-linecap = "round" aria-hidden = "true" >< path d = "M6 9l6 6 6-6" /></ svg >
</ span >
</ div >
< p class = "set-group-hint" > Models for orchestrator and background work. Leave on the default unless you are tuning cost.</ p >
< div class = "set-group-body" >
< div class = "set-row has-field" data-search = "default model tasks orchestrator" >
< div class = "set-row-text" >
< span class = "set-row-label" > Default for tasks</ span >
< span class = "set-row-desc" > Used for execution tasks. Optimizer suggestions stay advisory.</ span >
</ div >
< select id = "appSettingsDefaultModel" class = "set-select" >
< option value = "" > Default (CLI default)</ option >
< option value = "claude-fable-5" > Fable 5 (Most powerful)</ option >
< option value = "opus" > Opus (Most capable)</ option >
< option value = "sonnet" > Sonnet (Balanced)</ option >
< option value = "haiku" > Haiku (Fast & cheap)</ option >
</ select >
</ div >
< div class = "set-minigrid" data-search = "explore implement test review agent type overrides" >
< div class = "set-mini" >
< span class = "set-mini-label" > Explore</ span >
< select id = "appSettingsModelExplore" class = "set-select" >
< option value = "" > Use default</ option >
< option value = "haiku" > Haiku</ option >
< option value = "sonnet" > Sonnet</ option >
< option value = "opus" > Opus</ option >
< option value = "claude-fable-5" > Fable 5</ option >
</ select >
</ div >
< div class = "set-mini" >
< span class = "set-mini-label" > Implement</ span >
< select id = "appSettingsModelImplement" class = "set-select" >
< option value = "" > Use default</ option >
< option value = "haiku" > Haiku</ option >
< option value = "sonnet" > Sonnet</ option >
< option value = "opus" > Opus</ option >
< option value = "claude-fable-5" > Fable 5</ option >
</ select >
</ div >
< div class = "set-mini" >
< span class = "set-mini-label" > Test</ span >
< select id = "appSettingsModelTest" class = "set-select" >
< option value = "" > Use default</ option >
< option value = "haiku" > Haiku</ option >
< option value = "sonnet" > Sonnet</ option >
< option value = "opus" > Opus</ option >
< option value = "claude-fable-5" > Fable 5</ option >
</ select >
</ div >
< div class = "set-mini" >
< span class = "set-mini-label" > Review</ span >
< select id = "appSettingsModelReview" class = "set-select" >
< option value = "" > Use default</ option >
< option value = "haiku" > Haiku</ option >
< option value = "sonnet" > Sonnet</ option >
< option value = "opus" > Opus</ option >
< option value = "claude-fable-5" > Fable 5</ option >
</ select >
</ div >
</ div >
< div class = "set-row" data-search = "optimizer recommendations advisory" >
< div class = "set-row-text" >
< span class = "set-row-label" > Show optimizer recommendations</ span >
< span class = "set-row-desc" > Advisory only. Never overrides your choice.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsShowModelRecommendations" checked >< span class = "slider" ></ span ></ label >
</ div >
</ div >
</ div >
</ section >
<!-- ══ Agents & CLIs ═════════════════════════════════════════ -->
< section class = "set-section" id = "settings-clis" data-label = "Agents & CLIs" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M8 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2" />< path d = "M16 3h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-2" />< circle cx = "12" cy = "12" r = "2.5" /></ svg >
< h2 > Agents & CLIs</ h2 >
</ div >
< p class = "set-section-blurb" > Launch flags for the CLIs Codeman spawns.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Claude</ h4 >< span class = "set-scope" > synced</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row has-field" data-search = "startup mode permissions auto allowed tools" >
< div class = "set-row-text" >
< span class = "set-row-label" > Startup Mode</ span >
< span class = "set-row-desc" > How the CLI is launched in new sessions. Auto Mode runs without routine prompts behind a background safety classifier.</ span >
</ div >
< select id = "appSettingsClaudeMode" class = "set-select" >
< option value = "dangerously-skip-permissions" > Skip Permissions (default)</ option >
< option value = "auto" > Auto (classifier-guarded, low prompts)</ option >
< option value = "normal" > Normal (with prompts)</ option >
< option value = "allowedTools" > Allowed Tools Only</ option >
</ select >
</ div >
< div class = "set-row has-field" id = "allowedToolsRow" style = "display: none;" data-search = "allowed tools list" >
< div class = "set-row-text" >
< span class = "set-row-label" > Allowed Tools</ span >
< span class = "set-row-desc" > Comma-separated list of tools to allow.</ span >
</ div >
< input type = "text" id = "appSettingsAllowedTools" class = "set-input" placeholder = "Read,Glob,Grep,Bash" >
</ div >
< div class = "set-row" data-search = "ralph todo tracker loop" >
< div class = "set-row-text" >
< span class = "set-row-label" > Ralph / Todo Tracker</ span >
< span class = "set-row-desc" > Auto-enable for new sessions. Otherwise it enables itself on Ralph pattern detection.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsRalphEnabled" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "agent teams experimental" >
< div class = "set-row-text" >
< span class = "set-row-label" > Agent Teams</ span >
< span class = "set-row-desc" > Experimental multi-agent teams for all new Claude sessions.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsAgentTeams" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "agent skill codeman api workers" >
< div class = "set-row-text" >
< span class = "set-row-label" > Codeman Agent Skill</ span >
< span class = "set-row-desc" > Let new sessions start workers, send prompts and wait for results over the API.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsAgentSkill" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "remote auto reconnect ssh" >
< div class = "set-row-text" >
< span class = "set-row-label" > Remote auto-reconnect</ span >
< span class = "set-row-desc" > Reattach remote SSH sessions to their durable tmux after a dropped link.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsRemoteAutoReconnect" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "nice priority cpu" >
< div class = "set-row-text" >
< span class = "set-row-label" > Nice priority</ span >
< span class = "set-row-desc" > Lower the CPU priority of new sessions to reduce system impact.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsNiceEnabled" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" id = "niceValueRow" data-search = "nice value priority number" >
< div class = "set-row-text" >
< span class = "set-row-label" > Nice value</ span >
< span class = "set-row-desc" > -20 to 19. Higher means lower priority. Default 10.</ span >
</ div >
< input type = "number" id = "appSettingsNiceValue" class = "set-num" min = "-20" max = "19" value = "10" >
</ div >
</ div >
</ div >
< div class = "set-group" id = "appSettingsCodexGroup" >
< div class = "set-group-head" >< h4 > Codex</ h4 >< span class = "set-scope" > synced</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "codex bypass approvals sandbox" >
< div class = "set-row-text" >
< span class = "set-row-label" > Bypass approvals and sandbox</ span >
< span class = "set-row-desc" > Start new Codex sessions with --dangerously-bypass-approvals-and-sandbox.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsCodexDangerouslyBypassApprovals" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "codex animations status effects" >
< div class = "set-row-text" >
< span class = "set-row-label" > Animated status effects</ span >
< span class = "set-row-desc" > Decorative Codex TUI motion. Leave off to reduce remote and mobile redraws.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsCodexAnimations" >< span class = "slider" ></ span ></ label >
</ div >
</ div >
</ div >
</ section >
<!-- ══ Notifications ════════════════════════════════════════════ -->
< section class = "set-section" id = "settings-notifications" data-label = "Notifications" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< path d = "M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9" />< path d = "M13.7 21a2 2 0 0 1-3.4 0" /></ svg >
< h2 > Notifications</ h2 >
</ div >
< p class = "set-section-blurb" > What reaches you, and on which channel.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Channels</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "enable notifications master" >
< div class = "set-row-text" >
< span class = "set-row-label" > Notifications</ span >
< span class = "set-row-desc" > Master switch for every layer below.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsNotifEnabled" checked >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "browser notifications os permission" >
< div class = "set-row-text" >
< span class = "set-row-label" > Browser</ span >
< span class = "set-row-desc" > OS notifications while the tab is hidden. Remote access needs HTTPS: codeman web --https</ span >
</ div >
< div class = "set-row-actions" >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsNotifBrowser" >< span class = "slider" ></ span ></ label >
< button class = "btn-toolbar btn-sm" onclick = "app.notificationManager?.requestPermission()" title = "Request browser notification permission" > Ask</ button >
< span class = "settings-status" id = "notifPermissionStatus" > ?</ span >
</ div >
</ div >
< div class = "set-row" data-search = "push notifications subscribe pwa" >
< div class = "set-row-text" >
< span class = "set-row-label" > Push</ span >
< span class = "set-row-desc" > Works with the tab closed, and through a Cloudflare tunnel. Requires HTTPS or localhost; iOS requires the PWA.</ span >
</ div >
< div class = "set-row-actions" >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsPushEnabled" >< span class = "slider" ></ span ></ label >
< button class = "btn-toolbar btn-sm" id = "pushSubscribeBtn" onclick = "app.togglePushSubscription()" title = "Subscribe or unsubscribe to push notifications" > Subscribe</ button >
< span class = "settings-status" id = "pushSubscriptionStatus" > off</ span >
</ div >
</ div >
< div class = "set-row" data-search = "audio alerts beep sound" >
< div class = "set-row-text" >
< span class = "set-row-label" > Audio alerts</ span >
< span class = "set-row-desc" > Short beep on critical events.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsNotifAudio" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "idle threshold stuck minutes" >
< div class = "set-row-text" >
< span class = "set-row-label" > Idle threshold</ span >
< span class = "set-row-desc" > Notify when a session sits idle longer than this.</ span >
</ div >
< div class = "set-row-actions" >
< input type = "number" id = "appSettingsNotifStuckMins" class = "set-num" value = "10" min = "1" max = "120" >
< span class = "set-unit" > min</ span >
</ div >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Levels</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "set-chips" data-search = "critical warning info levels" >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsNotifCritical" checked >< span > Critical</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsNotifWarning" checked >< span > Warning</ span ></ label >
< label class = "set-chip" >< input type = "checkbox" id = "appSettingsNotifInfo" checked >< span > Info</ span ></ label >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Per event</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "event-type-grid" data-search = "per event permission question idle response respawn task subagent" >
< div class = "event-header" > Event</ div >
< div class = "event-header" > On</ div >
< div class = "event-header" > Browser</ div >
< div class = "event-header" > Push</ div >
< div class = "event-header" > Sound</ div >
< div class = "event-label" > Permission prompts</ div >
< input type = "checkbox" id = "eventPermissionEnabled" checked >
< input type = "checkbox" id = "eventPermissionBrowser" checked >
< input type = "checkbox" id = "eventPermissionPush" checked >
< input type = "checkbox" id = "eventPermissionAudio" checked >
< div class = "event-label" > Questions from Claude</ div >
< input type = "checkbox" id = "eventQuestionEnabled" checked >
< input type = "checkbox" id = "eventQuestionBrowser" checked >
< input type = "checkbox" id = "eventQuestionPush" checked >
< input type = "checkbox" id = "eventQuestionAudio" checked >
< div class = "event-label" > Session idle</ div >
< input type = "checkbox" id = "eventIdleEnabled" checked >
< input type = "checkbox" id = "eventIdleBrowser" checked >
< input type = "checkbox" id = "eventIdlePush" >
< input type = "checkbox" id = "eventIdleAudio" >
< div class = "event-label" > Response complete</ div >
< input type = "checkbox" id = "eventStopEnabled" >
< input type = "checkbox" id = "eventStopBrowser" >
< input type = "checkbox" id = "eventStopPush" >
< input type = "checkbox" id = "eventStopAudio" >
< div class = "event-label" > Respawn cycles</ div >
< input type = "checkbox" id = "eventRespawnEnabled" checked >
< input type = "checkbox" id = "eventRespawnBrowser" >
< input type = "checkbox" id = "eventRespawnPush" >
< input type = "checkbox" id = "eventRespawnAudio" >
< div class = "event-label" > Task complete</ div >
< input type = "checkbox" id = "eventRalphEnabled" checked >
< input type = "checkbox" id = "eventRalphBrowser" checked >
< input type = "checkbox" id = "eventRalphPush" >
< input type = "checkbox" id = "eventRalphAudio" checked >
< div class = "event-label" > Subagent activity</ div >
< input type = "checkbox" id = "eventSubagentEnabled" >
< input type = "checkbox" id = "eventSubagentBrowser" >
< input type = "checkbox" id = "eventSubagentPush" >
< input type = "checkbox" id = "eventSubagentAudio" >
</ div >
</ div >
</ div >
</ section >
<!-- ══ Voice ════════════════════════════════════════════════════ -->
< section class = "set-section" id = "settings-voice" data-label = "Voice" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< rect x = "9" y = "2" width = "6" height = "12" rx = "3" />< path d = "M5 11a7 7 0 0 0 14 0" />< path d = "M12 18v4" /></ svg >
< h2 > Voice</ h2 >
</ div >
< p class = "set-section-blurb" > Dictation into the composer.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Provider</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "voice provider active" >
< div class = "set-row-text" >< span class = "set-row-label" > Active provider</ span ></ div >
< span class = "voice-provider-status" id = "voiceProviderStatus" > — </ span >
</ div >
2026-08-10 11:19:51 +02:00
< div class = "set-row has-field" data-search = "voice provider claude deepgram web speech engine" >
< div class = "set-row-text" >
< span class = "set-row-label" > Speech engine</ span >
< span class = "set-row-desc" > Auto prefers Claude when this server can transcribe, then Deepgram, then the browser.</ span >
</ div >
< select id = "voiceProvider" class = "set-select" >
< option value = "auto" > Auto</ option >
< option value = "claude" > Claude</ option >
< option value = "deepgram" > Deepgram</ option >
< option value = "webspeech" > Browser (Web Speech)</ option >
</ select >
</ div >
2026-08-10 03:59:43 +02:00
< div class = "set-row has-field" data-search = "voice insert mode compose" >
< div class = "set-row-text" >
< span class = "set-row-label" > Insert mode</ span >
< span class = "set-row-desc" > Where dictated text lands.</ span >
</ div >
< select id = "voiceInsertMode" class = "set-select" >
< option value = "direct" > Direct to input</ option >
< option value = "compose" > Compose dialog</ option >
</ select >
</ div >
</ div >
</ div >
2026-08-10 11:19:51 +02:00
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Claude</ h4 >< span class = "set-scope" > synced</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "claude voice dictation subscription no api key" >
< div class = "set-row-text" >
< span class = "set-row-label" > Transcribe with this server's Claude login</ span >
< span class = "set-row-desc" > Dictation with no API key, through the same service Claude Code's own /voice mode uses. Microphone audio goes to Anthropic and is billed to this machine's Claude subscription, for everyone who can reach this UI.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsClaudeVoice" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" data-search = "claude voice status credentials" >
< div class = "set-row-text" >< span class = "set-row-label" > Server status</ span ></ div >
< span class = "voice-provider-status" id = "voiceClaudeStatus" data-i18n-skip > — </ span >
</ div >
</ div >
</ div >
2026-08-10 03:59:43 +02:00
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Deepgram Nova-3</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row has-field" data-search = "deepgram api key" >
< div class = "set-row-text" >
< span class = "set-row-label" > API key</ span >
< span class = "set-row-desc" > Stored on this device only, never sent to the server. Get a key at console.deepgram.com</ span >
</ div >
< div class = "set-row-actions set-row-actions-wide" >
< input type = "password" id = "voiceDeepgramKey" class = "set-input" placeholder = "Enter Deepgram API key" autocomplete = "off" >
< button class = "btn-toolbar btn-sm" onclick = "app.toggleDeepgramKeyVisibility()" id = "voiceKeyToggleBtn" type = "button" > Show</ button >
</ div >
</ div >
< div class = "set-row has-field" data-search = "voice language" >
< div class = "set-row-text" >< span class = "set-row-label" > Language</ span ></ div >
< select id = "voiceLanguage" class = "set-select" >
< option value = "en-US" > English (US)</ option >
< option value = "en-GB" > English (UK)</ option >
< option value = "es" > Spanish</ option >
< option value = "fr" > French</ option >
< option value = "de" > German</ option >
< option value = "ja" > Japanese</ option >
< option value = "multi" > Auto-detect (multi)</ option >
</ select >
</ div >
< div class = "set-row has-field" data-search = "voice keyterms domain keywords" >
< div class = "set-row-text" >
< span class = "set-row-label" > Domain keywords</ span >
< span class = "set-row-desc" > Comma-separated terms to boost recognition accuracy.</ span >
</ div >
< input type = "text" id = "voiceKeyterms" class = "set-input" placeholder = "codeman, tmux, respawn, subagent" >
</ div >
</ div >
</ div >
</ section >
<!-- ══ Shortcuts ════════════════════════════════════════════════ -->
< section class = "set-section" id = "settings-shortcuts" data-label = "Shortcuts" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< rect x = "2" y = "5" width = "20" height = "14" rx = "2" />< path d = "M6 9h.01M10 9h.01M14 9h.01M18 9h.01M6 13h.01M18 13h.01M9 13h6" /></ svg >
< h2 > Shortcuts</ h2 >
</ div >
< p class = "set-section-blurb" > Click a binding to capture a new key combination.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Keyboard</ h4 >< span class = "set-scope" > device</ span ></ div >
< div class = "set-group-body" >
< div id = "appSettingsShortcutsList" data-search = "keyboard shortcuts bindings keys" >
<!-- Populated by app.renderShortcutSettingsList() -->
</ div >
</ div >
</ div >
</ section >
<!-- ══ System ═══════════════════════════════════════════════════ -->
< section class = "set-section" id = "settings-system" data-label = "System" >
< div class = "set-section-head" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >< circle cx = "12" cy = "12" r = "3" />< path d = "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9c.26.6.86 1 1.51 1H21a2 2 0 0 1 0 4h-.09c-.65 0-1.25.4-1.51 1z" /></ svg >
< h2 > System</ h2 >
</ div >
< p class = "set-section-blurb" > Paths, automation, remote access and updates.</ p >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Paths</ h4 >< span class = "set-scope" > synced</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row has-field" data-search = "claude.md template path new cases" >
< div class = "set-row-text" >
< span class = "set-row-label" > CLAUDE.md template</ span >
< span class = "set-row-desc" > Used when creating new cases. Leave empty for the built-in template.</ span >
</ div >
< input type = "text" id = "appSettingsClaudeMdPath" class = "set-input" placeholder = "/path/to/CLAUDE.md template" >
</ div >
< div class = "set-row has-field" data-search = "default working directory" >
< div class = "set-row-text" >
< span class = "set-row-label" > Default working directory</ span >
< span class = "set-row-desc" > Starting directory for new sessions.</ span >
</ div >
< input type = "text" id = "appSettingsDefaultDir" class = "set-input" placeholder = "~/projects" >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Automation</ h4 >< span class = "set-scope" > synced</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "image watcher popup screenshots" >
< div class = "set-row-text" >
< span class = "set-row-label" > Image watcher</ span >
< span class = "set-row-desc" > Detect and pop up new images found in session directories.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsImageWatcherEnabled" >< span class = "slider" ></ span ></ label >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Remote access</ h4 >< span class = "set-scope" > synced</ span ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "cloudflare tunnel remote access url" >
< div class = "set-row-text" >
< span class = "set-row-label" > Cloudflare Tunnel</ span >
< span class = "set-row-desc" > Expose Codeman on a public URL. Set CODEMAN_PASSWORD first.</ span >
</ div >
< label class = "switch switch-sm" >< input type = "checkbox" id = "appSettingsTunnelEnabled" >< span class = "slider" ></ span ></ label >
</ div >
< div class = "set-row" id = "tunnelUrlRow" style = "display:none" >
< div class = "set-row-text" >< span class = "set-row-label" > Tunnel URL</ span ></ div >
< div class = "set-row-actions" >
< span id = "tunnelUrlDisplay" class = "set-copy" title = "Click to copy" ></ span >
< button class = "btn-icon-sm" id = "tunnelQrBtn" onclick = "app.showTunnelQR()" title = "Show QR code" > ⊞ </ button >
</ div >
</ div >
< div class = "set-row" id = "tunnelUploadUrlRow" style = "display:none" >
< div class = "set-row-text" >< span class = "set-row-label" > Upload URL</ span ></ div >
< span id = "tunnelUploadUrlDisplay" class = "set-copy" title = "Click to copy" ></ span >
</ div >
</ div >
</ div >
< div class = "set-group" >
< div class = "set-group-head" >< h4 > Updates</ h4 ></ div >
< div class = "set-group-body" >
< div class = "set-row" data-search = "version current release" >
< div class = "set-row-text" >< span class = "set-row-label" > Current version</ span ></ div >
< span class = "set-static mono" id = "updateCurrentVersion" > — </ span >
</ div >
< div class = "set-row" id = "updateCheckRow" data-search = "check for updates github release" >
< div class = "set-row-text" >
< span class = "set-row-label" > Check for updates</ span >
< span class = "set-row-desc" > Looks at GitHub for a newer Codeman release.</ span >
</ div >
< button class = "btn-toolbar btn-sm" id = "updateCheckBtn" onclick = "app.checkForUpdate()" > Check now</ button >
</ div >
< div id = "updateResult" class = "set-note" style = "display:none" ></ div >
< div class = "set-row" id = "updateActionRow" style = "display:none" >
< div class = "set-row-text" >< span class = "set-row-label" id = "updateActionLabel" > Update available</ span ></ div >
< button class = "btn-toolbar btn-sm btn-primary" id = "updateNowBtn" onclick = "app.startSelfUpdate()" > Update now</ button >
</ div >
< div id = "updateNotes" class = "set-notes" style = "display:none" ></ div >
< div id = "updateProgress" class = "set-progress" style = "display:none" ></ div >
</ div >
</ div >
</ section >
</ div >
</ div >
< div class = "form-actions set-foot" >
2026-01-19 02:08:58 +01:00
< button class = "btn-toolbar" onclick = "app.closeAppSettings()" > Cancel</ button >
< button class = "btn-toolbar btn-primary" onclick = "app.saveAppSettings()" > Save</ button >
</ div >
</ div >
</ div >
2026-07-09 11:26:24 -04:00
<!-- Shortcut Overlay Modal -->
< div class = "modal shortcut-overlay-modal" id = "shortcutOverlayModal" tabindex = "-1" >
< div class = "modal-backdrop" onclick = "app.closeShortcutOverlay()" ></ div >
< div class = "modal-content" >
< div class = "modal-header" >
< span class = "modal-title" > Keyboard Shortcuts</ span >
< button class = "modal-close" onclick = "app.closeShortcutOverlay()" aria-label = "Close" > ✕ </ button >
</ div >
< div class = "modal-body" >
< div id = "shortcutOverlayList" ></ div >
2026-07-12 19:58:23 +02:00
< div class = "shortcut-overlay-footer" >
< button class = "btn btn-sm" onclick = "app.closeShortcutOverlay(); app.showHelp()" > Full shortcut reference</ button >
</ div >
2026-07-09 11:26:24 -04:00
</ div >
</ div >
</ div >
2026-01-19 10:36:30 +01:00
<!-- Create Case Modal -->
< div class = "modal" id = "createCaseModal" >
< div class = "modal-backdrop" onclick = "app.closeCreateCaseModal()" ></ div >
2026-01-21 13:20:43 +01:00
< div class = "modal-content modal-lg" >
2026-01-19 10:36:30 +01:00
< div class = "modal-header" >
2026-01-21 13:20:43 +01:00
< h3 > Add Case</ h3 >
2026-01-30 14:14:45 +01:00
< button class = "modal-close" onclick = "app.closeCreateCaseModal()" aria-label = "Close create case" > × </ button >
2026-01-19 10:36:30 +01:00
</ div >
2026-01-21 13:20:43 +01:00
< div class = "modal-tabs" >
< button class = "modal-tab-btn active" data-tab = "case-create" > Create New</ button >
2026-08-09 16:33:01 +02:00
< button class = "modal-tab-btn" data-tab = "case-clone" id = "caseCloneTabBtn" > Clone Repo</ button >
2026-01-21 13:20:43 +01:00
< button class = "modal-tab-btn" data-tab = "case-link" > Link Existing</ button >
2026-07-12 19:49:39 +02:00
< button class = "modal-tab-btn" data-tab = "case-remote" > Remote</ button >
2026-07-19 17:53:14 +02:00
< button class = "modal-tab-btn" data-tab = "case-docker" > Docker</ button >
2026-04-03 09:45:19 +08:00
< button class = "modal-tab-btn" data-tab = "case-manage" > Manage</ button >
2026-01-21 13:20:43 +01:00
</ div >
2026-01-19 10:36:30 +01:00
< div class = "modal-body" >
2026-01-21 13:20:43 +01:00
<!-- Create New Tab -->
< div class = "modal-tab-content" id = "case-create" >
< div class = "form-row" >
< label > Case Name</ label >
2026-02-18 05:02:27 +01:00
< input type = "text" id = "newCaseName" placeholder = "my-project" pattern = "[a-zA-Z0-9_-]+" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
2026-02-26 16:44:34 +01:00
< span class = "form-hint" > Letters, numbers, hyphens, underscores only. Created in ~/codeman-cases/</ span >
2026-01-21 13:20:43 +01:00
</ div >
< div class = "form-row" >
< label > Description (optional)</ label >
2026-02-18 05:02:27 +01:00
< input type = "text" id = "newCaseDescription" placeholder = "A brief description..." autocomplete = "off" >
2026-01-21 13:20:43 +01:00
</ div >
2026-07-20 01:28:53 +02:00
< div class = "form-row docker-quick-row" >
< label class = "checkbox-row" >< input type = "checkbox" id = "newCaseDocker" > 🐳 Run in an isolated Docker container</ label >
2026-07-20 02:36:02 +02:00
< span class = "form-hint" > Runs this case in a hardened, isolated container. The base image is built automatically on first use. Docker/Podman must be installed.</ span >
2026-07-19 19:28:13 +02:00
</ div >
2026-07-20 01:28:53 +02:00
< details class = "advanced-options docker-quick-settings" id = "dockerQuickSettings" >
< summary > Container settings (optional, sensible defaults)</ summary >
2026-07-19 21:35:38 +02:00
< div class = "advanced-options-content" >
< div class = "form-row" >
< label > Template</ label >
< select id = "quickDockerTemplate" onchange = "app.applyDockerTemplate()" >
< option value = "small" > Small — 2 GB RAM, 1 CPU</ option >
< option value = "medium" selected > Medium — 4 GB RAM, 2 CPU (default)</ option >
< option value = "large" > Large — 8 GB RAM, 4 CPU</ option >
< option value = "gpu" > GPU — 8 GB RAM, 4 CPU, all GPUs</ option >
< option value = "custom" > Custom</ option >
</ select >
2026-07-20 01:28:53 +02:00
< span class = "form-hint" > Disk is elastic: storage grows automatically as data flows in (no fixed cap).</ span >
2026-07-19 21:35:38 +02:00
</ div >
< div class = "form-row" >
< label > Memory</ label >
< input type = "text" id = "quickDockerMemory" placeholder = "4g" autocomplete = "off" spellcheck = "false" >
</ div >
< div class = "form-row" >
< label > CPUs</ label >
< input type = "text" id = "quickDockerCpus" placeholder = "2" autocomplete = "off" spellcheck = "false" >
</ div >
< div class = "form-row" >
< label > GPUs</ label >
< input type = "text" id = "quickDockerGpus" placeholder = "none (e.g. all, or 1)" autocomplete = "off" spellcheck = "false" >
< span class = "form-hint" > Needs the NVIDIA container toolkit on the host.</ span >
</ div >
< div class = "form-row" >
< label > Network</ label >
< select id = "quickDockerNetwork" >
< option value = "bridge" > bridge (internet on)</ option >
< option value = "none" > none (fully isolated)</ option >
</ select >
</ div >
< div class = "form-row" >
< label > Image</ label >
< input type = "text" id = "quickDockerImage" placeholder = "codeman/agent:base" autocomplete = "off" spellcheck = "false" >
</ div >
< div class = "form-row" >
< label class = "checkbox-row" >< input type = "checkbox" id = "quickDockerMountCreds" checked > Mount host credentials (~/.claude etc.)</ label >
</ div >
</ div >
</ details >
2026-01-19 10:36:30 +01:00
</ div >
2026-08-09 16:33:01 +02:00
<!-- Clone Repo Tab (issue #236) -->
< div class = "modal-tab-content hidden" id = "case-clone" >
< div class = "form-row" >
< label > Repository URL</ label >
< input type = "url" id = "cloneRepoUrl" placeholder = "https://github.com/owner/repo.git" autocomplete = "off" autocapitalize = "off" autocorrect = "off" spellcheck = "false" oninput = "app.onCloneUrlInput()" >
< span class = "form-hint clone-status" id = "cloneRepoStatus" > Public repositories only: Codeman clones with no credentials.</ span >
</ div >
< div class = "form-row" >
< label > Case Name</ label >
< input type = "text" id = "cloneCaseName" placeholder = "repo" pattern = "[a-zA-Z0-9_-]+" autocomplete = "off" autocapitalize = "off" spellcheck = "false" oninput = "app.onCloneNameEdited()" >
< span class = "form-hint" > Filled in from the URL. Cloned into ~/codeman-cases/< name> , so deleting the case deletes this working tree.</ span >
</ div >
< div class = "form-row" >
< label > Branch or Tag (optional)</ label >
< input type = "text" id = "cloneRepoRef" list = "cloneRepoRefOptions" placeholder = "default branch" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
< datalist id = "cloneRepoRefOptions" ></ datalist >
< span class = "form-hint" id = "cloneRefHint" > Leave blank for the repository's default branch.</ span >
</ div >
< div class = "form-row" >
< label > Brain</ label >
< select id = "cloneCaseBrain" class = "form-select" >
< option value = "" > Leave the Run button as it is</ option >
< option value = "claude" data-cli = "claude" > Claude Code</ option >
< option value = "codex" data-cli = "codex" > Codex</ option >
< option value = "gemini" data-cli = "gemini" > Gemini</ option >
< option value = "opencode" data-cli = "opencode" > OpenCode</ option >
< option value = "antigravity" data-cli = "antigravity" > Antigravity</ option >
< option value = "shell" > Shell (no agent)</ option >
</ select >
< span class = "form-hint" > Which CLI to point the Run button at once the clone finishes. Changeable any time from the Run dropdown.</ span >
</ div >
< details class = "advanced-options" >
< summary > Clone options</ summary >
< div class = "advanced-options-content" >
< div class = "form-row" >
< label class = "checkbox-row" >< input type = "checkbox" id = "cloneShallow" > Shallow clone (--depth 1)</ label >
< span class = "form-hint" > Much faster on big repositories, but there is no history to read afterwards.</ span >
</ div >
< div class = "form-row" >
< label class = "checkbox-row" >< input type = "checkbox" id = "cloneStartSession" > Start a session when the clone finishes</ label >
</ div >
</ div >
</ details >
< span class = "form-hint" id = "cloneCaseNote" style = "margin-top: 8px; display: block;" > The clone runs while this request is open, so a large repository takes a while. The case appears as soon as git finishes, even if the browser gave up waiting.</ span >
</ div >
2026-01-21 13:20:43 +01:00
<!-- Link Existing Tab -->
< div class = "modal-tab-content hidden" id = "case-link" >
< div class = "form-row" >
< label > Case Name</ label >
2026-02-18 05:02:27 +01:00
< input type = "text" id = "linkCaseName" placeholder = "my-project" pattern = "[a-zA-Z0-9_-]+" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
2026-02-26 16:44:34 +01:00
< span class = "form-hint" > Name to identify this case in Codeman</ span >
2026-01-21 13:20:43 +01:00
</ div >
< div class = "form-row" >
< label > Folder Path</ label >
2026-07-20 19:11:31 +08:00
< div class = "path-input-group" >
< input type = "text" id = "linkCasePath" placeholder = "/mnt/d/AI/my-project" autocomplete = "off" autocapitalize = "off" autocorrect = "off" spellcheck = "false" >
< button type = "button" class = "btn path-input-browse" onclick = "app.openLinkCasePathPicker()" > Browse… </ button >
</ div >
< span class = "form-hint" > Choose an existing folder from this computer or enter its absolute path</ span >
2026-01-21 13:20:43 +01:00
</ div >
2026-01-19 10:36:30 +01:00
</ div >
2026-06-11 18:43:39 +00:00
<!-- Remote Tab -->
< div class = "modal-tab-content hidden" id = "case-remote" >
< div class = "form-row" >
< label > Case Name</ label >
< input type = "text" id = "remoteCaseName" placeholder = "gpu-work" pattern = "[a-zA-Z0-9_-]+" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
< span class = "form-hint" > Name to identify this remote case in Codeman</ span >
</ div >
< div class = "form-row" >
< label > Remote Path</ label >
< input type = "text" id = "remoteCasePath" placeholder = "/home/user/projects/work" autocomplete = "off" autocapitalize = "off" autocorrect = "off" spellcheck = "false" >
< span class = "form-hint" > Absolute path on the remote host. Codeman will not create or delete it.</ span >
</ div >
< div class = "form-row" >
< label > Host ID</ label >
< input type = "text" id = "remoteHostId" placeholder = "gpu-box" pattern = "[a-zA-Z0-9_-]+" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
</ div >
< div class = "form-row" >
< label > SSH Host/IP</ label >
< input type = "text" id = "remoteHostAddress" placeholder = "10.0.0.42" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
</ div >
< div class = "form-row" >
< label > SSH Username</ label >
< input type = "text" id = "remoteHostUsername" placeholder = "ubuntu" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
</ div >
2026-06-13 10:21:56 -04:00
< div class = "form-row" >
< label > SSH Port</ label >
< input type = "number" id = "remoteHostPort" placeholder = "22" min = "1" max = "65535" autocomplete = "off" >
< span class = "form-hint" > Optional. Leave blank for the default port 22.</ span >
</ div >
2026-06-11 18:43:39 +00:00
< div class = "form-row" >
< label > Codex Command Override</ label >
< input type = "text" id = "remoteHostCodexCommand" placeholder = "exec codx personal" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
< span class = "form-hint" > Optional. Leave blank to use exec codex on the remote host.</ span >
</ div >
2026-06-13 10:21:56 -04:00
< details class = "advanced-options" >
< summary > Advanced SSH</ summary >
< div class = "advanced-options-content" >
< div class = "form-row" >
< label > Identity File</ label >
< input type = "text" id = "remoteHostIdentityFile" placeholder = "~/.ssh/remote_ed25519" autocomplete = "off" autocapitalize = "off" autocorrect = "off" spellcheck = "false" >
< span class = "form-hint" > Optional. Path to a private key on this machine (passed to ssh -i). Never the key contents.</ span >
</ div >
< div class = "form-row" >
< label > SOCKS Proxy</ label >
< input type = "text" id = "remoteHostSocksProxy" placeholder = "127.0.0.1:1080" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
< span class = "form-hint" > Optional. host:port of a SOCKS5 proxy (e.g. cloudflared). Routes ssh through it via a ProxyCommand.</ span >
</ div >
< div class = "form-row" >
< label > Jump Host</ label >
< input type = "text" id = "remoteHostJumpHost" placeholder = "bastion@10.0.0.1:22" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
< span class = "form-hint" > Optional. [user@]host[:port] for ssh -J (jump/bastion host).</ span >
</ div >
< div class = "form-row" >
< label > Extra -o Options</ label >
< textarea id = "remoteHostExtraSshOptions" rows = "3" placeholder = "StrictHostKeyChecking=accept-new ConnectTimeout=10" autocomplete = "off" autocapitalize = "off" spellcheck = "false" ></ textarea >
< span class = "form-hint" > Optional. One KEY=VALUE per line; each becomes an ssh -o option.</ span >
</ div >
</ div >
</ details >
2026-06-13 11:04:18 -04:00
<!-- COD-105 — discover + attach existing remote tmux sessions this Codeman didn't create. -->
< details class = "advanced-options" id = "remoteDiscoverSection" >
< summary > Discover existing sessions</ summary >
< div class = "advanced-options-content" >
< span class = "form-hint" > Find < code > codeman-*</ code > tmux sessions already running on this host (started by the remote's own Codeman or another instance) and attach to one. Attaching shares the session; closing the tab detaches it — it is never killed.</ span >
< div class = "form-row" style = "margin-top: 8px;" >
< button type = "button" class = "btn-toolbar" id = "remoteDiscoverBtn" onclick = "app.discoverRemoteSessions()" > Discover existing sessions</ button >
</ div >
< div id = "remoteDiscoverResults" class = "remote-discover-results" ></ div >
</ div >
</ details >
2026-06-11 18:43:39 +00:00
</ div >
2026-07-19 17:53:14 +02:00
<!-- Docker Tab -->
< div class = "modal-tab-content hidden" id = "case-docker" >
< div class = "form-row" >
< label > Case Name</ label >
< input type = "text" id = "dockerCaseName" placeholder = "sandbox" pattern = "[a-zA-Z0-9_-]+" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
< span class = "form-hint" > Runs inside an isolated container. Multiple sessions can share the same container.</ span >
</ div >
< div class = "form-row" >
< label > Workspace Path</ label >
< input type = "text" id = "dockerWorkspacePath" placeholder = "/home/user/projects/sandbox" autocomplete = "off" autocapitalize = "off" autocorrect = "off" spellcheck = "false" >
< span class = "form-hint" > Absolute HOST directory, bind-mounted into the container. Codeman scaffolds CLAUDE.md + hooks into it.</ span >
</ div >
< div class = "form-row" >
< label > Host ID</ label >
< input type = "text" id = "dockerHostId" placeholder = "local" pattern = "[a-zA-Z0-9_-]+" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
< span class = "form-hint" > A reusable docker host profile. Reuse the same ID across cases to share settings.</ span >
</ div >
< div class = "form-row" >
< label > Image</ label >
< input type = "text" id = "dockerImage" placeholder = "codeman/agent:base" autocomplete = "off" autocapitalize = "off" spellcheck = "false" >
2026-08-06 07:21:57 +02:00
< span class = "form-hint" > Build it once with < code > node scripts/build-agent-image.mjs</ code > . Contains node + claude/codex/gemini/opencode/agy + tmux.</ span >
2026-07-19 17:53:14 +02:00
</ div >
< div class = "form-row" >
< label > Network</ label >
< select id = "dockerNetwork" >
< option value = "bridge" > bridge (internet on, default)</ option >
< option value = "none" > none (fully isolated, no network)</ option >
< option value = "custom" > custom bridge</ option >
</ select >
</ div >
< details class = "advanced-options" >
< summary > Advanced container settings</ summary >
< div class = "advanced-options-content" >
< div class = "form-row" >
< label > Memory</ label >
< input type = "text" id = "dockerMemory" placeholder = "4g" autocomplete = "off" spellcheck = "false" >
< span class = "form-hint" > Optional, e.g. 4g / 512m. Enforced as a hard OOM cap where the engine supports it.</ span >
</ div >
< div class = "form-row" >
< label > CPUs</ label >
< input type = "text" id = "dockerCpus" placeholder = "2" autocomplete = "off" spellcheck = "false" >
</ div >
< div class = "form-row" >
< label class = "checkbox-row" >< input type = "checkbox" id = "dockerMountCredentials" checked > Mount host credentials (~/.claude etc.)</ label >
< span class = "form-hint" > On: your existing login just works (creds stay on the host, never in exports). Off: sealed sandbox, log in inside the container.</ span >
</ div >
< div class = "form-row" >
< label class = "checkbox-row" >< input type = "checkbox" id = "dockerResumeOnStart" checked > Resume last conversation on relaunch</ label >
</ div >
</ div >
</ details >
< span class = "form-hint" id = "dockerLinkStatus" style = "margin-top: 8px; display: block;" ></ span >
</ div >
2026-04-03 09:45:19 +08:00
<!-- Manage Tab -->
< div class = "modal-tab-content hidden" id = "case-manage" >
< div class = "case-manage-list" id = "caseManageList" >
<!-- Populated by JS -->
</ div >
2026-04-03 03:58:29 +02:00
< span class = "form-hint" style = "margin-top: 8px; display: block;" > Use arrows to reorder. Changes are saved automatically.</ span >
2026-07-19 17:53:14 +02:00
< div id = "dockerExportsSection" style = "margin-top: 16px; border-top: 1px solid var(--border, #333); padding-top: 12px;" >
< div style = "display:flex; align-items:center; justify-content:space-between; margin-bottom:8px;" >
< strong style = "font-size: 13px;" > Docker exports</ strong >
< button class = "btn-toolbar" onclick = "app.refreshDockerExports()" > Refresh</ button >
</ div >
< div class = "case-manage-list" id = "dockerExportsList" >< span class = "form-hint" > No exports yet. Export a docker case from its tab.</ span ></ div >
</ div >
2026-04-03 09:45:19 +08:00
</ div >
2026-01-19 10:36:30 +01:00
</ div >
< div class = "form-actions" >
< button class = "btn-toolbar" onclick = "app.closeCreateCaseModal()" > Cancel</ button >
2026-01-21 13:20:43 +01:00
< button class = "btn-toolbar btn-primary" id = "caseModalSubmit" onclick = "app.submitCaseModal()" > Create</ button >
2026-01-19 10:36:30 +01:00
</ div >
</ div >
</ div >
2026-01-31 22:17:47 +01:00
<!-- Mobile Case Picker (Bottom Sheet) -->
< div class = "modal mobile-case-picker" id = "mobileCasePickerModal" >
< div class = "modal-backdrop" onclick = "app.closeMobileCasePicker()" ></ div >
< div class = "mobile-case-picker-sheet" >
< div class = "mobile-case-picker-header" >
< h3 > Select Case</ h3 >
< button class = "modal-close" onclick = "app.closeMobileCasePicker()" aria-label = "Close case picker" > × </ button >
</ div >
< div class = "mobile-case-picker-body" >
< div class = "mobile-case-list" id = "mobileCaseList" >
<!-- Cases populated by JS -->
</ div >
</ div >
< div class = "mobile-case-picker-footer" >
< button class = "btn-toolbar btn-case-create-mobile" onclick = "app.showCreateCaseFromMobile()" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< line x1 = "12" y1 = "5" x2 = "12" y2 = "19" />< line x1 = "5" y1 = "12" x2 = "19" y2 = "12" /></ svg >
Create New Case
</ button >
</ div >
</ div >
</ div >
2026-01-26 23:37:14 +01:00
<!-- Ralph Loop Wizard Modal -->
< div class = "modal" id = "ralphWizardModal" >
2026-01-28 02:31:58 +01:00
< div class = "modal-backdrop" ></ div >
2026-01-27 01:58:02 +01:00
< div class = "modal-content modal-wizard" >
2026-01-26 23:37:14 +01:00
< div class = "modal-header" >
< h3 > Start Ralph Loop</ h3 >
2026-01-28 04:04:40 +01:00
< div class = "modal-header-actions" >
2026-01-30 14:14:45 +01:00
< button class = "modal-close" onclick = "app.closeRalphWizard()" aria-label = "Close Ralph wizard" > × </ button >
2026-01-28 04:04:40 +01:00
</ div >
2026-01-26 23:37:14 +01:00
</ div >
<!-- Wizard Progress Steps -->
< div class = "wizard-progress" >
< div class = "wizard-step active" data-step = "1" >
< span class = "wizard-step-number" > 1</ span >
2026-01-27 01:58:02 +01:00
< span class = "wizard-step-label" > Describe</ span >
2026-01-26 23:37:14 +01:00
</ div >
< div class = "wizard-step" data-step = "2" >
< span class = "wizard-step-number" > 2</ span >
2026-01-27 01:58:02 +01:00
< span class = "wizard-step-label" > Plan</ span >
</ div >
< div class = "wizard-step" data-step = "3" >
< span class = "wizard-step-number" > 3</ span >
2026-01-26 23:37:14 +01:00
< span class = "wizard-step-label" > Launch</ span >
</ div >
</ div >
< div class = "modal-body" >
<!-- Step 1: Task & Completion Setup -->
2026-01-27 01:58:02 +01:00
< div class = "wizard-page wizard-page-compact" id = "ralphWizardStep1" >
< div class = "wizard-step-intro" >
< p > Describe your task below. Claude will generate an implementation plan with testing steps.</ p >
</ div >
2026-01-26 23:37:14 +01:00
< div class = "form-row" >
2026-01-27 01:58:02 +01:00
< label > What do you want to build?</ label >
2026-01-26 23:37:14 +01:00
< div class = "textarea-with-assist" >
< textarea
id = "ralphTaskDescription"
2026-01-28 02:31:58 +01:00
rows = "3"
2026-01-27 01:58:02 +01:00
placeholder = "Build a REST API for user authentication with JWT tokens, login/logout endpoints, and password reset flow." ></ textarea >
2026-01-26 23:37:14 +01:00
</ div >
</ div >
2026-01-27 01:58:02 +01:00
< div class = "wizard-inline-row" >
< div class = "wizard-inline-field" >
2026-01-27 11:02:02 +01:00
< label > Working Directory < span id = "existingPlanBadge" class = "existing-plan-badge" style = "display:none" ></ span ></ label >
2026-01-27 01:58:02 +01:00
< div class = "case-select-inline" >
2026-01-27 11:02:02 +01:00
< select id = "ralphCaseSelect" class = "form-select" onchange = "app.onRalphCaseChange()" ></ select >
2026-01-27 01:58:02 +01:00
< button class = "btn-toolbar btn-sm" onclick = "app.showCreateCaseModal()" > +</ button >
</ div >
2026-01-26 23:37:14 +01:00
</ div >
2026-01-27 01:58:02 +01:00
< div class = "wizard-inline-field wizard-inline-field-small" >
< label > Iterations</ label >
< div class = "iteration-presets-compact" >
< button type = "button" class = "iteration-preset-btn active" data-iterations = "10" onclick = "app.selectIterationPreset(10)" > 10</ button >
< button type = "button" class = "iteration-preset-btn" data-iterations = "25" onclick = "app.selectIterationPreset(25)" > 25</ button >
< button type = "button" class = "iteration-preset-btn" data-iterations = "50" onclick = "app.selectIterationPreset(50)" > 50</ button >
< button type = "button" class = "iteration-preset-btn" data-iterations = "0" onclick = "app.selectIterationPreset(0)" > ∞ </ button >
</ div >
</ div >
</ div >
<!-- Hidden completion phrase with default -->
< input type = "hidden" id = "ralphCompletionPhrase" value = "COMPLETE" >
</ div >
<!-- Step 2: Plan Generation -->
< div class = "wizard-page wizard-page-compact hidden" id = "ralphWizardStep2" >
2026-01-27 11:02:02 +01:00
<!-- Existing plan detected (shown when @fix_plan.md exists) -->
< div id = "existingPlanSection" class = "plan-section hidden" >
<!-- Content populated by app.updateExistingPlanUI() -->
</ div >
<!-- Loading state (shown during generation) -->
< div id = "planGenerationLoading" class = "plan-loading-state hidden" >
2026-01-27 01:58:02 +01:00
< div class = "plan-loading-content" >
< div class = "plan-spinner" ></ div >
< div class = "plan-loading-text" >
2026-01-27 02:38:37 +01:00
< span class = "plan-loading-title" id = "planLoadingTitle" > Starting Opus 4.5...</ span >
2026-01-27 01:58:02 +01:00
< span class = "plan-loading-time" id = "planLoadingTime" > 0s</ span >
</ div >
</ div >
2026-01-27 02:38:37 +01:00
< p class = "plan-loading-hint" id = "planLoadingHint" > Initializing deep reasoning model</ p >
2026-01-27 23:15:25 +01:00
< div class = "plan-loading-actions" >
< button class = "btn-toolbar btn-sm plan-cancel-btn" onclick = "app.cancelPlanGeneration()" > Stop</ button >
< button class = "btn-toolbar btn-sm" onclick = "app.regeneratePlan()" > Regenerate</ button >
</ div >
2026-01-28 04:17:14 +01:00
<!-- Auto-start toggle visible during generation -->
< div class = "plan-autostart-toggle" >
< label class = "plan-autostart-label" >
2026-02-27 08:54:00 +01:00
< input type = "checkbox" id = "ralphAutoStartStep2" onchange = "app.ralphWizardConfig.autoStart = this.checked" >
2026-01-28 04:17:14 +01:00
< span class = "plan-autostart-text" > Auto-launch when plan completes</ span >
</ label >
</ div >
2026-01-26 23:37:14 +01:00
</ div >
2026-01-27 23:15:25 +01:00
<!-- Stopped/Error state -->
2026-01-27 01:58:02 +01:00
< div id = "planGenerationError" class = "plan-section hidden" >
2026-01-27 23:15:25 +01:00
< div class = "plan-stopped-indicator" id = "planStoppedIndicator" >
< span class = "plan-stopped-icon" > ■ </ span >
< span class = "plan-stopped-text" > Stopped</ span >
</ div >
2026-01-27 01:58:02 +01:00
< p class = "plan-error-msg" id = "planErrorMsg" ></ p >
< div class = "plan-actions" >
2026-01-27 23:15:25 +01:00
< button class = "btn-toolbar btn-primary" onclick = "app.generatePlan()" > Regenerate Plan</ button >
2026-01-26 23:37:14 +01:00
</ div >
2026-01-27 23:15:25 +01:00
< p class = "plan-skip-hint" > Use < strong > Back</ strong > to edit your task, or < strong > Next</ strong > to continue without a plan</ p >
2026-01-26 23:37:14 +01:00
</ div >
2026-01-27 01:58:02 +01:00
<!-- Editor state -->
< div id = "planEditor" class = "plan-section hidden" >
< div class = "plan-editor-header" >
2026-01-27 11:02:02 +01:00
< span > Implementation Plan < span class = "plan-stats" id = "planStats" ></ span ></ span >
2026-01-27 01:58:02 +01:00
< div class = "plan-editor-actions" >
< div class = "plan-option-group" >
< label > Detail:</ label >
< div class = "plan-detail-btns" >
2026-01-27 23:15:25 +01:00
< button type = "button" class = "plan-detail-btn" data-detail = "standard" onclick = "app.setPlanDetail('standard')" title = "Single-pass generation with Opus 4.5" > Standard</ button >
2026-02-27 08:54:00 +01:00
< button type = "button" class = "plan-detail-btn active" data-detail = "detailed" onclick = "app.setPlanDetail('detailed')" title = "Enhanced: parallel subagents + verification (slower but more thorough)" > Enhanced</ button >
2026-01-27 01:58:02 +01:00
</ div >
</ div >
< button class = "btn-toolbar btn-sm" onclick = "app.regeneratePlan()" > Regenerate</ button >
</ div >
</ div >
< div id = "planItemsList" class = "plan-items-list" ></ div >
2026-01-26 23:37:14 +01:00
</ div >
</ div >
2026-01-27 01:58:02 +01:00
<!-- Step 3: Preview & Launch -->
< div class = "wizard-page hidden" id = "ralphWizardStep3" >
2026-01-26 23:37:14 +01:00
< div class = "ralph-preview-section" >
< label > Prompt Preview</ label >
< div class = "ralph-prompt-preview" id = "ralphPromptPreview" ></ div >
</ div >
< div class = "ralph-config-summary" >
< div class = "config-summary-row" >
< span class = "config-label" > Completion Phrase:</ span >
< code id = "summaryPhrase" > COMPLETE</ code >
</ div >
< div class = "config-summary-row" >
< span class = "config-label" > Max Iterations:</ span >
< span id = "summaryIterations" > 10</ span >
</ div >
< div class = "config-summary-row" >
< span class = "config-label" > Case:</ span >
< span id = "summaryCase" > testcase</ span >
</ div >
2026-01-27 01:58:02 +01:00
< div class = "config-summary-row" style = "display: none;" >
< span class = "config-label" > Plan:</ span >
< span id = "summaryPlan" > -</ span >
</ div >
2026-01-26 23:37:14 +01:00
</ div >
<!-- Advanced Options (Collapsible) -->
< details class = "advanced-options" >
< summary > Advanced Options</ summary >
< div class = "advanced-options-content" >
< div class = "form-row form-row-switch" >
< label > Enable Respawn</ label >
< label class = "switch" >
2026-01-27 17:46:43 +01:00
< input type = "checkbox" id = "ralphEnableRespawn" >
2026-01-26 23:37:14 +01:00
< span class = "slider" ></ span >
</ label >
2026-01-27 17:46:43 +01:00
< span class = "form-hint" > Auto-restart sessions when context fills up (usually not needed)</ span >
2026-01-26 23:37:14 +01:00
</ div >
</ div >
</ details >
</ div >
</ div >
<!-- Wizard Footer -->
< div class = "wizard-footer" >
< button class = "btn-toolbar" id = "ralphBackBtn" onclick = "app.ralphWizardBack()" style = "display: none;" > Back</ button >
< div class = "wizard-footer-right" >
< button class = "btn-toolbar" onclick = "app.closeRalphWizard()" > Cancel</ button >
< button class = "btn-toolbar btn-primary" id = "ralphNextBtn" onclick = "app.ralphWizardNext()" > Next</ button >
< button class = "btn-toolbar btn-success" id = "ralphStartBtn" onclick = "app.startRalphLoop()" style = "display: none;" > Start Loop</ button >
</ div >
</ div >
</ div >
</ div >
2026-01-28 06:03:48 +01:00
2026-01-19 10:36:30 +01:00
<!-- Kill All Confirmation Modal -->
< div class = "modal" id = "killAllModal" >
< div class = "modal-backdrop" onclick = "app.closeKillAllModal()" ></ div >
< div class = "modal-content modal-sm" >
< div class = "modal-header" >
< h3 > Kill All Sessions</ h3 >
2026-01-30 14:14:45 +01:00
< button class = "modal-close" onclick = "app.closeKillAllModal()" aria-label = "Close kill all modal" > × </ button >
2026-01-19 10:36:30 +01:00
</ div >
< div class = "modal-body" >
< p class = "modal-warning-text" > Choose how to close < span id = "killAllCount" > 0</ span > session(s):</ p >
</ div >
< div class = "close-options" >
< button class = "close-option" onclick = "app.confirmKillAll(false)" >
< span class = "close-option-title" > Remove All Tabs</ span >
2026-02-12 18:29:49 +01:00
< span class = "close-option-desc" > Tmux sessions keep running in background</ span >
2026-01-19 10:36:30 +01:00
</ button >
< button class = "close-option close-option-danger" onclick = "app.confirmKillAll(true)" >
2026-02-12 18:29:49 +01:00
< span class = "close-option-title" > Kill All Claude + Tmux</ span >
2026-01-19 10:36:30 +01:00
< span class = "close-option-desc" > Terminate everything completely</ span >
</ button >
</ div >
< div class = "modal-footer-cancel" >
< button class = "btn-toolbar btn-sm" onclick = "app.closeKillAllModal()" > Cancel</ button >
</ div >
</ div >
</ div >
2026-01-24 01:22:53 +01:00
<!-- Notification Drawer -->
< div class = "notification-drawer" id = "notifDrawer" >
< div class = "notif-drawer-header" >
< span class = "notif-drawer-title" > Notifications</ span >
< div class = "notif-drawer-actions" >
< button class = "btn-notif-action" onclick = "app.notificationManager?.markAllRead()" title = "Mark all read" > ✓ </ button >
< button class = "btn-notif-action" onclick = "app.notificationManager?.clearAll()" title = "Clear all" > 🗑 </ button >
< button class = "btn-notif-action" onclick = "app.toggleNotifications()" title = "Close" > × </ button >
</ div >
</ div >
< div class = "notif-drawer-list" id = "notifList" ></ div >
< div class = "notif-drawer-empty" id = "notifEmpty" > No notifications</ div >
</ div >
2026-06-07 01:21:17 -04:00
<!-- Away Digest Modal -->
< div class = "modal" id = "awayDigestModal" >
< div class = "modal-backdrop" onclick = "app.closeAwayDigest()" ></ div >
< div class = "modal-content away-digest-modal" >
< div class = "modal-header" >
< h3 > Away Digest</ h3 >
< div class = "modal-header-actions" >
< button class = "btn-toolbar btn-sm" onclick = "app.loadAwayDigest()" title = "Refresh away digest" > ↻ Refresh</ button >
< button class = "modal-close" onclick = "app.closeAwayDigest()" aria-label = "Close away digest" > × </ button >
</ div >
</ div >
< div class = "modal-body" >
< div class = "away-digest-ranges" role = "group" aria-label = "Away digest range" >
< button class = "filter-btn active" data-away-range = "since-last-visit" onclick = "app.setAwayDigestRange('since-last-visit')" > Since last visit</ button >
< button class = "filter-btn" data-away-range = "1h" onclick = "app.setAwayDigestRange('1h')" > Last hour</ button >
< button class = "filter-btn" data-away-range = "today" onclick = "app.setAwayDigestRange('today')" > Today</ button >
< button class = "filter-btn" data-away-range = "24h" onclick = "app.setAwayDigestRange('24h')" > 24h</ button >
< button class = "filter-btn" data-away-range = "custom" onclick = "app.setAwayDigestRange('custom')" > Custom</ button >
</ div >
< div class = "away-digest-custom-range" id = "awayDigestCustomRange" >
< label >
Since
< input type = "datetime-local" id = "awayDigestCustomSince" >
</ label >
< label >
Until
< input type = "datetime-local" id = "awayDigestCustomUntil" >
</ label >
</ div >
< div class = "away-digest-summary" id = "awayDigestSummary" ></ div >
< div class = "away-digest-freshness" id = "awayDigestFreshness" ></ div >
< div class = "away-digest-sections" id = "awayDigestSections" >
< p class = "empty-message" > Open the digest to load recent activity</ p >
</ div >
</ div >
</ div >
</ div >
2026-07-01 10:05:58 +00:00
<!-- Command Palette Modal -->
< div class = "modal command-palette-modal" id = "commandPaletteModal" >
< div class = "modal-backdrop" onclick = "app.closeCommandPalette()" ></ div >
< div class = "command-palette-shell" role = "dialog" aria-modal = "true" aria-labelledby = "commandPaletteTitle" >
< div class = "command-palette-input-row" >
< span class = "command-palette-search-icon" aria-hidden = "true" > ⌕</ span >
< input type = "search" id = "commandPaletteSearch" class = "command-palette-search" placeholder = "Search open sessions or start a new one" autocomplete = "off" maxlength = "160" aria-labelledby = "commandPaletteTitle" >
< kbd > Esc</ kbd >
</ div >
< div class = "command-palette-label" id = "commandPaletteTitle" > Open sessions</ div >
< div id = "commandPaletteList" class = "command-palette-list" ></ div >
</ div >
</ div >
2026-06-18 12:28:14 -04:00
<!-- Session Manager Modal -->
< div class = "modal" id = "sessionManagerModal" >
< div class = "modal-backdrop" onclick = "app.closeSessionManager()" ></ div >
< div class = "modal-content session-manager-modal" >
< div class = "modal-header" >
< h3 > Sessions</ h3 >
< button class = "modal-close" onclick = "app.closeSessionManager()" aria-label = "Close session manager" > × </ button >
</ div >
< div class = "modal-body" >
< input type = "search" id = "sessionManagerSearch" class = "search-input" placeholder = "Search sessions by name, prompt, or path…" autocomplete = "off" maxlength = "200" >
< div id = "sessionManagerList" class = "session-manager-list" ></ div >
</ div >
</ div >
</ div >
2026-07-01 10:05:58 +00:00
2026-01-25 20:36:04 +01:00
<!-- Token Stats Modal -->
< div class = "modal" id = "tokenStatsModal" >
< div class = "modal-backdrop" onclick = "app.closeTokenStats()" ></ div >
< div class = "modal-content token-stats-modal" >
< div class = "modal-header" >
< h3 > Token Usage Statistics</ h3 >
2026-01-30 14:14:45 +01:00
< button class = "modal-close" onclick = "app.closeTokenStats()" aria-label = "Close token statistics" > × </ button >
2026-01-25 20:36:04 +01:00
</ div >
< div class = "modal-body" >
< div class = "stats-summary" id = "statsSummary" ></ div >
< div class = "stats-chart-container" >
< div class = "stats-chart-label" > Last 7 Days</ div >
< div class = "stats-chart" id = "statsChart" ></ div >
< div class = "stats-chart-days" id = "statsChartDays" ></ div >
</ div >
< div class = "stats-table-container" >
< div class = "stats-table-label" > Daily Breakdown</ div >
< div class = "stats-table" id = "statsTable" ></ div >
</ div >
</ div >
</ div >
</ div >
2026-01-18 05:14:34 +01:00
</ div >
2026-01-30 14:14:45 +01:00
<!-- Save Preset Modal -->
< div class = "modal" id = "savePresetModal" >
< div class = "modal-backdrop" onclick = "app.closeSavePresetModal()" ></ div >
< div class = "modal-content modal-sm" >
< div class = "modal-header" >
< h3 > Save Respawn Preset</ h3 >
< button class = "modal-close" onclick = "app.closeSavePresetModal()" aria-label = "Close" > × </ button >
</ div >
< div class = "modal-body" >
< div class = "form-row" >
< label > Preset Name</ label >
< input type = "text" id = "presetNameInput" placeholder = "My Custom Preset" maxlength = "30" >
</ div >
< div class = "form-row" >
< label > Description (optional)</ label >
< input type = "text" id = "presetDescriptionInput" placeholder = "When to use this preset" maxlength = "100" >
</ div >
</ div >
< div class = "modal-footer" >
< button class = "btn btn-secondary" onclick = "app.closeSavePresetModal()" > Cancel</ button >
< button class = "btn btn-primary" onclick = "app.confirmSavePreset()" > Save Preset</ button >
</ div >
</ div >
</ div >
2026-02-19 19:55:04 +01:00
<!-- Session Lifecycle Audit Log (floating window) -->
< div id = "lifecycleWindow" style = "display:none;position:fixed;z-index:2000;top:60px;left:50%;transform:translateX(-50%);width:900px;max-width:95vw;min-width:400px;min-height:250px;height:520px;background:#111;border:1px solid #333;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.6);resize:both;overflow:hidden" >
< div id = "lifecycleWindowHeader" style = "display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background:#1a1a2e;border-bottom:1px solid #333;cursor:move;user-select:none;border-radius:8px 8px 0 0" >
< span style = "color:#e0e0e0;font-size:13px;font-weight:600" > Session Lifecycle Log</ span >
< div style = "display:flex;gap:8px;align-items:center" >
< div style = "display:flex;gap:6px;flex-wrap:nowrap" >
< select id = "lifecycleFilterEvent" style = "background:#0d0d0d;color:#e0e0e0;border:1px solid #444;border-radius:4px;padding:2px 6px;font-size:11px" >
2026-02-19 18:30:14 +01:00
< option value = "" > All Events</ option >
< option value = "created" > Created</ option >
< option value = "started" > Started</ option >
< option value = "exit" > Exit</ option >
< option value = "deleted" > Deleted</ option >
< option value = "recovered" > Recovered</ option >
< option value = "stale_cleaned" > Stale Cleaned</ option >
< option value = "mux_died" > Mux Died</ option >
< option value = "server_started" > Server Started</ option >
< option value = "server_stopped" > Server Stopped</ option >
</ select >
2026-02-19 19:55:04 +01:00
< input id = "lifecycleFilterSession" type = "text" placeholder = "Session ID..." style = "background:#0d0d0d;color:#e0e0e0;border:1px solid #444;border-radius:4px;padding:2px 6px;font-size:11px;width:120px" >
< button onclick = "app.loadLifecycleLog()" style = "background:#333;color:#ccc;border:1px solid #444;border-radius:4px;padding:2px 8px;font-size:11px;cursor:pointer" > Refresh</ button >
2026-02-19 18:30:14 +01:00
</ div >
2026-02-19 19:55:04 +01:00
< button onclick = "app.closeLifecycleLog()" style = "background:none;border:none;color:#888;font-size:18px;cursor:pointer;padding:0 4px;line-height:1" aria-label = "Close" > × </ button >
2026-02-19 18:30:14 +01:00
</ div >
</ div >
2026-02-19 19:55:04 +01:00
< div style = "overflow:auto;height:calc(100% - 36px);padding:4px 0" >
< table id = "lifecycleTable" style = "width:100%;border-collapse:collapse;font-size:12px;font-family:monospace" >
< thead >
< tr style = "color:#888;text-align:left;border-bottom:1px solid #333;position:sticky;top:0;background:#111" >
< th style = "padding:4px 8px" > Time</ th >
< th style = "padding:4px 8px" > Event</ th >
< th style = "padding:4px 8px" > Session</ th >
< th style = "padding:4px 8px" > Reason</ th >
< th style = "padding:4px 8px" > Extra</ th >
</ tr >
</ thead >
< tbody id = "lifecycleTableBody" ></ tbody >
</ table >
< div id = "lifecycleEmpty" style = "color:#666;text-align:center;padding:20px;display:none" > No lifecycle entries found</ div >
</ div >
2026-02-19 18:30:14 +01:00
</ div >
2026-01-25 23:41:59 +01:00
<!-- SVG Overlay for Subagent Connection Lines -->
< svg id = "connectionLines" class = "connection-lines-svg" >
<!-- Lines drawn dynamically -->
</ svg >
2026-08-09 15:55:35 +02:00
<!-- Connection-loss overlay: the app shell is served from the service-worker
cache, so Codeman renders even with nothing reachable. Without this, that
looks like an empty dashboard rather than a dead connection. Only shown
while no server state has loaded this page load. -->
< div class = "offline-overlay" id = "offlineOverlay" hidden >
< div class = "offline-overlay-card" role = "alertdialog" aria-labelledby = "offlineOverlayTitle" aria-describedby = "offlineOverlayBody" >
< div class = "offline-overlay-icon" aria-hidden = "true" >
< svg width = "46" height = "46" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.7" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M1 1l22 22" />
< path d = "M16.72 11.06A10.94 10.94 0 0 1 19 12.55" />
< path d = "M5 12.55a10.94 10.94 0 0 1 5.17-2.39" />
< path d = "M10.71 5.05A16 16 0 0 1 22.58 9" />
< path d = "M1.42 9a15.91 15.91 0 0 1 4.7-2.88" />
< path d = "M8.53 16.11a6 6 0 0 1 6.95 0" />
< line x1 = "12" y1 = "20" x2 = "12.01" y2 = "20" />
</ svg >
</ div >
< h2 class = "offline-overlay-title" id = "offlineOverlayTitle" > Can't reach the Codeman server</ h2 >
< p class = "offline-overlay-body" id = "offlineOverlayBody" ></ p >
< div class = "offline-overlay-host" id = "offlineOverlayHost" ></ div >
< ul class = "offline-overlay-hints" >
< li > Check Wi-Fi or mobile data</ li >
< li > Check your VPN / Tailscale is connected</ li >
< li > Check the Codeman server is still running</ li >
</ ul >
< div class = "offline-overlay-actions" >
< button class = "offline-overlay-btn offline-overlay-btn--primary" id = "offlineOverlayRetry" onclick = "app.retryConnection()" > Retry now</ button >
< button class = "offline-overlay-btn" onclick = "app.dismissOfflineOverlay()" > Show cached view</ button >
</ div >
< div class = "offline-overlay-status" id = "offlineOverlayStatus" > Retrying…</ div >
</ div >
</ div >
2026-08-09 14:04:34 +02:00
<!-- Approvals Inbox drawer (populated by approvals-ui.js; opened from the header bell) -->
2026-08-09 23:05:11 +02:00
<!-- Read My Mind: predicted-next-prompt modal (readmymind-ui.js). Suggestion
text is set via value/textContent only: predictor output derives from
observable (injectable) content, and the explicit click here is the
security boundary (nothing is ever auto-sent). -->
< div class = "modal" id = "readMyMindModal" >
< div class = "modal-backdrop" onclick = "app.closeReadMyMind()" ></ div >
< div class = "modal-content readmymind-modal" >
< div class = "modal-header" >
< h3 >< span aria-hidden = "true" > 🧠</ span > Read My Mind</ h3 >
< button class = "modal-close" onclick = "app.closeReadMyMind()" aria-label = "Close" > × </ button >
</ div >
< div class = "modal-body" >
< div class = "readmymind-loading" > Reading your mind…</ div >
< div class = "readmymind-result" style = "display:none" >
< div class = "readmymind-suggestion-row" >
< span class = "readmymind-kind" id = "readMyMindKind" data-i18n-skip > continue</ span >
< input type = "text" id = "readMyMindPrompt" class = "readmymind-prompt-input" data-i18n-skip aria-label = "Predicted prompt, editable" onkeydown = "if(event.key==='Enter')app.sendReadMyMind(true)" >
</ div >
< div class = "readmymind-why" id = "readMyMindWhy" data-i18n-skip ></ div >
2026-08-10 03:57:06 +02:00
< div class = "readmymind-alternates" id = "readMyMindAlternates" data-i18n-skip style = "display:none" ></ div >
2026-08-09 23:05:11 +02:00
</ div >
< div class = "readmymind-error" style = "display:none" > No suggestion this time. Rethink to try again.</ div >
</ div >
< div class = "modal-footer" >
< button class = "btn btn-secondary" onclick = "app.closeReadMyMind()" > Dismiss</ button >
< button class = "btn btn-secondary" id = "readMyMindRethink" onclick = "app.rethinkReadMyMind()" > Rethink</ button >
< button class = "btn btn-secondary" onclick = "app.sendReadMyMind(false)" title = "Put the text on the session's composer without submitting it" > Insert</ button >
< button class = "btn btn-primary" onclick = "app.sendReadMyMind(true)" > Send</ button >
</ div >
</ div >
</ div >
2026-08-09 14:04:34 +02:00
< div class = "approvals-drawer" id = "approvalsDrawer" role = "complementary" aria-label = "Approvals inbox" >
< div class = "approvals-header" >
< div >
< div class = "approvals-title" > Approvals</ div >
< div class = "approvals-subtitle" > Prompts waiting on you, across all sessions</ div >
</ div >
< button class = "approvals-close" onclick = "app.closeApprovalsInbox()" title = "Close" aria-label = "Close approvals inbox" > ✕</ button >
</ div >
< div class = "approvals-list" ></ div >
</ div >
2026-03-11 19:13:49 +01:00
< script defer src = "constants.js" ></ script >
2026-07-21 02:49:25 +08:00
< script defer src = "i18n.js" ></ script >
2026-03-11 19:13:49 +01:00
< script defer src = "mobile-handlers.js" ></ script >
< script defer src = "voice-input.js" ></ script >
< script defer src = "notification-manager.js" ></ script >
< script defer src = "keyboard-accessory.js" ></ script >
2026-03-14 16:23:25 +09:00
< script defer src = "input-cjk.js" ></ script >
2026-06-14 12:33:48 -04:00
<!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
< script defer src = "sanitize-html.js" ></ script >
2026-03-11 19:13:49 +01:00
< script defer src = "app.js" ></ script >
2026-03-14 21:24:22 +01:00
< script defer src = "terminal-ui.js" ></ script >
< script defer src = "respawn-ui.js" ></ script >
< script defer src = "ralph-panel.js" ></ script >
2026-03-21 07:20:18 +01:00
< script defer src = "orchestrator-panel.js" ></ script >
2026-06-29 11:35:56 +05:30
< script defer src = "cron-ui.js" ></ script >
2026-03-14 21:24:22 +01:00
< script defer src = "settings-ui.js" ></ script >
< script defer src = "panels-ui.js" ></ script >
2026-08-09 23:05:11 +02:00
< script defer src = "readmymind-ui.js" ></ script >
2026-06-15 08:40:05 +02:00
< script defer src = "ultracode-panel.js" ></ script >
2026-08-09 14:04:34 +02:00
< script defer src = "approvals-ui.js" ></ script >
2026-07-20 04:31:30 +02:00
< script defer src = "admin-ui.js" ></ script >
2026-03-14 21:24:22 +01:00
< script defer src = "session-ui.js" ></ script >
2026-07-27 17:06:36 +02:00
< script defer src = "webview-tabs.js" ></ script >
2026-08-04 15:02:46 +02:00
< script defer src = "mobile-overview.js" ></ script >
2026-08-09 18:17:42 +02:00
< script defer src = "home-sessions.js" ></ script >
2026-08-04 12:31:52 +02:00
< script defer src = "entrance-animations.js" ></ script >
2026-03-11 19:13:49 +01:00
< script defer src = "ralph-wizard.js" ></ script >
< script defer src = "api-client.js" ></ script >
< script defer src = "subagent-windows.js" ></ script >
2026-06-15 16:33:54 +02:00
< script defer src = "ultracode-windows.js" ></ script >
2026-05-16 23:57:07 -04:00
< script defer src = "image-input.js" ></ script >
2026-01-18 05:14:34 +01:00
</ body >
</ html >