mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +02:00
Both are read by every engine (the Claude path sends the language as its base tag and the keyterms as a recognition hint), but they sat under the "Deepgram Nova-3" heading, which read as if they only applied to Deepgram. That group now holds just the API key. Ids are unchanged, so the getElementById load/save contract in settings-ui.js is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2984 lines
191 KiB
HTML
2984 lines
191 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<!-- 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="/">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||
<meta name="description" content="Claude Code session manager with web interface">
|
||
<meta name="theme-color" content="#11151c">
|
||
<meta name="google" content="notranslate">
|
||
<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">
|
||
<link rel="manifest" href="manifest.json">
|
||
<title>Codeman</title>
|
||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
|
||
<link rel="stylesheet" href="styles.css">
|
||
<link rel="stylesheet" href="mobile.css" media="(max-width: 1023px)">
|
||
<!-- 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>
|
||
<!-- 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">
|
||
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
|
||
'defer' preserves execution order (xterm loads before fit addon). -->
|
||
<script defer src="vendor/xterm.min.js"></script>
|
||
<script defer src="vendor/xterm-addon-fit.min.js"></script>
|
||
<!-- 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>
|
||
<!-- WebGL addon lazy-loaded by app.js on desktop only (skipped on mobile, saving 244KB) -->
|
||
<script defer src="vendor/xterm-addon-unicode11.min.js"></script>
|
||
<script defer src="vendor/xterm-zerolag-input.js"></script>
|
||
<script defer src="vendor/xterm-predictive-echo.js"></script>
|
||
<script defer src="vendor/marked.min.js"></script>
|
||
<!-- 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>
|
||
<!-- 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>
|
||
<!-- Synchronous skin selection — runs before first paint to prevent theme flash -->
|
||
<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>
|
||
<!-- 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>
|
||
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
|
||
<style>
|
||
.loading-skeleton{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg-dark,#11151c)}
|
||
.skeleton-header{height:40px;background:var(--glass-bg,rgba(31,38,48,0.85));border-bottom:1px solid var(--glass-border,rgba(255,255,255,0.08));display:flex;align-items:center;padding:0 12px}
|
||
.skeleton-brand{color:var(--accent,#38b6f0);font-size:14px;font-weight:700;font-family:'Manrope',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;opacity:.85}
|
||
.skeleton-tabs{display:flex;gap:4px;margin-left:16px}
|
||
.skeleton-tab{width:80px;height:24px;background:var(--control-bg,rgba(255,255,255,0.04));border-radius:6px}
|
||
.skeleton-terminal{flex:1;background:var(--term-bg,#161b23)}
|
||
.skeleton-toolbar{height:42px;background:var(--glass-bg,rgba(31,38,48,0.85));border-top:1px solid var(--glass-border,rgba(255,255,255,0.08))}
|
||
.app-loaded .loading-skeleton{display:none}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Loading skeleton — visible instantly before CSS/JS loads, hidden by app-loaded class -->
|
||
<div class="loading-skeleton">
|
||
<div class="skeleton-header">
|
||
<span class="skeleton-brand">Codeman</span>
|
||
<div class="skeleton-tabs"><div class="skeleton-tab"></div></div>
|
||
</div>
|
||
<div class="skeleton-terminal"></div>
|
||
<div class="skeleton-toolbar"></div>
|
||
</div>
|
||
<!-- Skip link for keyboard users -->
|
||
<!-- 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>
|
||
<div class="app">
|
||
<!-- Compact Header with Session Tabs -->
|
||
<header class="header">
|
||
<div class="header-brand">
|
||
<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
|
||
>
|
||
</div>
|
||
|
||
<!-- Session Tabs -->
|
||
<div class="session-tabs" id="sessionTabs" role="tablist" aria-label="Session tabs">
|
||
</div>
|
||
|
||
<!-- Detached single-session window title (shown only in solo mode) -->
|
||
<div class="solo-session-title" id="soloSessionTitle" style="display: none;" aria-live="polite"></div>
|
||
|
||
<div class="header-right" id="headerRight">
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<div class="connection-indicator" id="connectionIndicator" style="display: none;">
|
||
<span class="connection-dot" id="connectionDot"></span>
|
||
<span class="connection-text" id="connectionText"></span>
|
||
</div>
|
||
<div class="header-font-controls">
|
||
<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>
|
||
</div>
|
||
<div class="header-system-stats" id="headerSystemStats" title="System resource usage">
|
||
<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>
|
||
<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>
|
||
</div>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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">
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<div class="header-plan-usage header-plan-usage--hidden" id="planUsageChip" title="Claude plan usage limits">—</div>
|
||
<button class="btn-icon-header btn-notifications" onclick="app.toggleNotifications()" title="Notifications" aria-label="Toggle notifications" style="display:none;">
|
||
<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="notification-badge" id="notifBadge" style="display:none;">0</span>
|
||
</button>
|
||
<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>
|
||
<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>
|
||
<div class="header-tokens" id="headerTokens" style="display: none" title="Total tokens across all sessions">0 tokens</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Timer Banner (shown when timed run is active) -->
|
||
<div class="timer-banner" id="timerBanner" style="display: none;">
|
||
<div class="timer-content">
|
||
<span class="timer-value" id="timerValue">00:00:00</span>
|
||
<div class="timer-progress"><div class="timer-progress-fill" id="timerProgress"></div></div>
|
||
<span class="timer-meta" id="timerMeta">0 tasks | $0.00</span>
|
||
</div>
|
||
<button class="btn-icon-only" onclick="app.stopCurrentRun()" title="Stop" aria-label="Stop current run">■</button>
|
||
</div>
|
||
|
||
<!-- Respawn Banner - Compact 2-column layout -->
|
||
<div class="respawn-banner" id="respawnBanner" style="display: none;">
|
||
<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>
|
||
<button class="btn-icon-only" onclick="app.stopRespawn()" title="Stop Respawn" aria-label="Stop respawn">■</button>
|
||
</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>
|
||
<div class="respawn-action-log" id="respawnActionLog"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Ralph / Todo Tracker Panel -->
|
||
<div class="ralph-panel collapsed" id="ralphStatePanel" style="display: none;">
|
||
<!-- Collapsed Summary Bar -->
|
||
<div class="ralph-summary" id="ralphSummary">
|
||
<div class="ralph-summary-content" onclick="app.toggleRalphStatePanel()">
|
||
<div class="ralph-ring-mini" id="ralphRingMini">
|
||
<svg viewBox="0 0 36 36" class="ralph-ring-svg">
|
||
<defs>
|
||
<linearGradient id="ralphGradientMini" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" stop-color="#3ec8ee" />
|
||
<stop offset="100%" stop-color="#2b8fd9" />
|
||
</linearGradient>
|
||
</defs>
|
||
<circle class="ralph-ring-bg" cx="18" cy="18" r="15.9" />
|
||
<circle class="ralph-ring-progress-mini" cx="18" cy="18" r="15.9" id="ralphRingMiniProgress" style="stroke-dashoffset: 100" />
|
||
</svg>
|
||
<span class="ralph-ring-text" id="ralphRingMiniText">0%</span>
|
||
</div>
|
||
<div class="ralph-summary-info">
|
||
<span class="ralph-title">Ralph / Todo Tracker</span>
|
||
<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>
|
||
<span class="ralph-toggle" id="ralphToggle">▼</span>
|
||
</div>
|
||
<div class="ralph-controls">
|
||
<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>
|
||
<button class="btn-icon-sm" onclick="event.stopPropagation(); app.toggleRalphDetach()" title="Detach/Attach panel" id="ralphDetachBtn">⧉</button>
|
||
<button class="btn-icon-sm" onclick="event.stopPropagation(); app.closeRalphTracker()" title="Close tracker">×</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Expanded Detail View - Compact horizontal layout -->
|
||
<div class="ralph-detail" id="ralphDetail">
|
||
<div class="ralph-detail-row">
|
||
<!-- Progress Ring -->
|
||
<div class="ralph-ring-compact">
|
||
<svg viewBox="0 0 100 100" class="ralph-ring-svg-large">
|
||
<defs>
|
||
<linearGradient id="ralphGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" stop-color="#3ec8ee" />
|
||
<stop offset="100%" stop-color="#2b8fd9" />
|
||
</linearGradient>
|
||
</defs>
|
||
<circle class="ralph-ring-track" cx="50" cy="50" r="42" />
|
||
<circle class="ralph-ring-fill" cx="50" cy="50" r="42" id="ralphRingProgress" style="stroke-dashoffset: 264" />
|
||
</svg>
|
||
<div class="ralph-ring-center">
|
||
<div class="ralph-ring-percent" id="ralphRingPercent">0%</div>
|
||
<div class="ralph-ring-label">Complete</div>
|
||
</div>
|
||
</div>
|
||
<!-- Meta Info + Tasks unified -->
|
||
<div class="ralph-info-unified">
|
||
<div class="ralph-info-labels">
|
||
<div class="ralph-info-row">
|
||
<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>
|
||
<code id="ralphPhrase">--</code>
|
||
</div>
|
||
<div class="ralph-info-row">
|
||
<span class="ralph-info-label">Elapsed</span>
|
||
<span id="ralphElapsed">0m</span>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
<div class="ralph-tasks-grid" id="ralphTasksGrid"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Main Terminal Area -->
|
||
<main class="main">
|
||
<div class="terminal-wrap">
|
||
<div class="terminal-container" id="terminalContainer"></div>
|
||
<textarea id="cjkInput" rows="1" placeholder="CJK input (Enter = send, Esc = clear)"
|
||
maxlength="65536" aria-label="CJK IME input field"
|
||
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Welcome Overlay (shown when no session active) -->
|
||
<div class="welcome-overlay" id="welcomeOverlay">
|
||
<!-- Open tabs as a rail docked to the left edge (home-sessions.js).
|
||
Absolutely positioned so the centered content below never moves, and
|
||
therefore only rendered where the gutter is wider than the rail;
|
||
ships `hidden` and only that module reveals it. -->
|
||
<aside class="home-sessions" id="homeSessions" hidden></aside>
|
||
<div class="welcome-content">
|
||
<h1 class="welcome-title">Codeman</h1>
|
||
<p class="welcome-desc">Manage AI Coding tools in persistent tmux sessions.</p>
|
||
<div class="welcome-actions">
|
||
<button class="welcome-btn welcome-btn-claude" id="welcomeClaudeBtn" style="display: none;" onclick="app.setRunMode('claude'); app.runClaude()">
|
||
<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 Claude Code
|
||
</button>
|
||
<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>
|
||
<button class="welcome-btn welcome-btn-opencode" id="welcomeOpencodeBtn" style="display: none;" onclick="app.setRunMode('opencode'); app.runOpenCode()">
|
||
<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>
|
||
<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>
|
||
<button class="welcome-btn welcome-btn-gemini" id="welcomeGeminiBtn" style="display: none;" onclick="app.setRunMode('gemini'); app.runGemini()">
|
||
<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>
|
||
</div>
|
||
<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>
|
||
<div class="history-sessions" id="historySessions" style="display:none">
|
||
<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>
|
||
<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>
|
||
<div class="history-list" id="historyList"></div>
|
||
</div>
|
||
<p class="welcome-hint">Or click Run to start</p>
|
||
<button class="welcome-ralph-link" onclick="app.showRalphWizard()">Start Ralph Loop →</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
</main>
|
||
|
||
<!-- 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>
|
||
</div>
|
||
<div class="project-insights-body" id="projectInsightsList"></div>
|
||
<div class="project-insights-resize-handle"></div>
|
||
</div>
|
||
|
||
<!-- 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">
|
||
<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>
|
||
<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">
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<div class="file-preview-footer" id="filePreviewFooter"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Bottom Toolbar -->
|
||
<footer class="toolbar">
|
||
<div class="toolbar-left">
|
||
<!-- Run AI -->
|
||
<div class="toolbar-group">
|
||
<div class="run-btn-group">
|
||
<button class="btn-toolbar btn-run" id="runBtn" onclick="app.run()" title="Run">
|
||
<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>
|
||
<button class="run-mode-option" data-mode="codex" onclick="app.setRunMode('codex')">
|
||
<span class="run-mode-dot codex"></span>Codex
|
||
</button>
|
||
<button class="run-mode-option" data-mode="gemini" onclick="app.setRunMode('gemini')">
|
||
<span class="run-mode-dot gemini"></span>Gemini
|
||
</button>
|
||
<button class="run-mode-option" data-mode="antigravity" onclick="app.setRunMode('antigravity')">
|
||
<span class="run-mode-dot antigravity"></span>Antigravity
|
||
</button>
|
||
<div class="run-mode-sep"></div>
|
||
<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>
|
||
<!-- 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>
|
||
<div class="run-mode-header">Recent Sessions</div>
|
||
<div class="run-mode-history" id="runModeHistory"></div>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
<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>
|
||
<button class="btn-toolbar btn-shell" onclick="app.runShell()" title="Run Shell">
|
||
Run Shell
|
||
</button>
|
||
<!-- 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>
|
||
<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">
|
||
<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">
|
||
<option value="testcase">testcase</option>
|
||
</select>
|
||
<button class="btn-case-add" onclick="app.showCreateCaseModal()" title="Create new case">+</button>
|
||
<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>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<!-- Mobile-only case button + gear -->
|
||
<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>
|
||
<button class="btn-settings-mobile" onclick="app.openAppSettings()" title="App Settings">⚙</button>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
</div>
|
||
</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()">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toolbar-center">
|
||
<button class="btn-toolbar btn-sm btn-voice" id="voiceInputBtn" onclick="VoiceInput.toggle()"
|
||
title="Voice input (Ctrl+Shift+V)" 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>
|
||
</div>
|
||
|
||
<div class="toolbar-right">
|
||
<!-- Orchestrator button hidden until feature is ready -->
|
||
<!-- <button class="btn-toolbar btn-sm" onclick="app.toggleOrchestratorPanel()" title="Orchestrator Loop">⚙ Orchestrator</button> -->
|
||
<button class="btn-toolbar btn-sm btn-cron btn-cron--hidden" onclick="app.openCron()" title="Cron Jobs">⏰ Cron</button>
|
||
<span class="version-display" id="versionDisplay" title="Codeman version">v0.0.0</span>
|
||
</div>
|
||
</footer>
|
||
|
||
<!-- 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>
|
||
<button class="modal-close" onclick="app.closeHelp()" aria-label="Close help">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<section class="shortcut-section">
|
||
<h4>Session</h4>
|
||
<div class="shortcuts-grid">
|
||
<div><kbd>Ctrl</kbd>+<kbd>W</kbd></div><div>Close Session</div>
|
||
<div><kbd>Ctrl/Cmd/Option</kbd>+<kbd>K</kbd></div><div>Find Open Session</div>
|
||
<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">
|
||
<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>
|
||
<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>
|
||
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>R</kbd></div><div>Restore Terminal Size</div>
|
||
<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>
|
||
<div><kbd>Shift</kbd>+<kbd>Wheel</kbd></div><div>Scroll local history (when mouse passthrough is active)</div>
|
||
</div>
|
||
</section>
|
||
<section class="shortcut-section">
|
||
<h4>Panels</h4>
|
||
<div class="shortcuts-grid">
|
||
<div><kbd>Ctrl</kbd>+<kbd>?</kbd></div><div>Show Shortcuts</div>
|
||
<div><kbd>Alt/Option</kbd>+<kbd>?</kbd></div><div>Show Shortcuts</div>
|
||
<div><kbd>Escape</kbd></div><div>Close Panels</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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
|
||
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.
|
||
</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>
|
||
|
||
<!-- Cron Jobs Modal -->
|
||
<div class="modal" id="cronModal">
|
||
<div class="modal-backdrop" onclick="app.closeCron()"></div>
|
||
<div class="modal-content modal-lg">
|
||
<div class="modal-header">
|
||
<h3>Cron Jobs</h3>
|
||
<button class="modal-close" onclick="app.closeCron()" aria-label="Close cron">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="form-hint cron-modal-hint">Times use the server's local timezone.</p>
|
||
<div class="cron-toolbar">
|
||
<button class="btn-toolbar btn-primary" onclick="app.openCronJobForm()">+ New Job</button>
|
||
<button class="btn-toolbar" onclick="app.refreshCron()">Refresh</button>
|
||
</div>
|
||
<!-- Job list -->
|
||
<div id="cronJobList" class="cron-job-list"></div>
|
||
|
||
<!-- Create/Edit form (hidden until New/Edit) -->
|
||
<div id="cronJobForm" class="cron-job-form hidden">
|
||
<div class="cron-form-title" id="cronFormTitle">New Cron Job</div>
|
||
<input type="hidden" id="schJobId">
|
||
|
||
<div class="form-section-header">Basics</div>
|
||
<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>
|
||
<select id="schAgentType" class="form-select" onchange="app.onCronAgentTypeChange()">
|
||
<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>
|
||
<option value="antigravity">Antigravity</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row"><label>Working Directory</label><input type="text" id="schWorkingDir" placeholder="/absolute/path"></div>
|
||
<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>
|
||
|
||
<div class="form-section-header">Prompt</div>
|
||
<div class="form-row"><label>Prompt Source</label>
|
||
<select id="schPromptMode" class="form-select" onchange="app.onCronPromptModeChange()">
|
||
<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>
|
||
<select id="schInputMode" class="form-select">
|
||
<option value="typed">Typed (via tmux)</option>
|
||
<option value="paste">Paste (direct)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-section-header">Schedule</div>
|
||
<div class="form-row"><label>Schedule Type</label>
|
||
<select id="schScheduleType" class="form-select" onchange="app.onCronScheduleTypeChange()">
|
||
<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>
|
||
<span id="schWeeklyDays" class="cron-weekdays">
|
||
<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>
|
||
|
||
<div class="form-section-header">Options</div>
|
||
<div class="form-row"><label>On auto-run, if same agent running</label>
|
||
<select id="schConcurrencyPolicy" class="form-select">
|
||
<option value="warn_only">Run anyway</option>
|
||
<option value="skip_if_same_agent_running">Skip this run</option>
|
||
</select>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
<div class="form-row"><label>Notes</label><input type="text" id="schNotes" placeholder="Optional"></div>
|
||
|
||
<div id="cronFormError" class="form-hint cron-form-error"></div>
|
||
<div class="cron-form-actions">
|
||
<button class="btn-toolbar" onclick="app.cancelCronJobForm()">Cancel</button>
|
||
<button class="btn-toolbar btn-primary" onclick="app.saveCronJob()">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Monitor Panel (Tmux Sessions + Background Tasks) -->
|
||
<div class="monitor-panel" id="monitorPanel">
|
||
<div class="monitor-panel-header" id="monitorPanelHeader">
|
||
<div class="monitor-panel-title">Monitor</div>
|
||
<div class="monitor-panel-actions">
|
||
<button class="btn-toolbar btn-sm btn-danger" onclick="app.killAllMuxSessions()" title="Kill all sessions and their tmux processes">Kill All</button>
|
||
<button class="btn-icon-sm" onclick="app.reconcileMuxSessions()" title="Refresh tmux sessions">↻</button>
|
||
<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>
|
||
<button class="btn-icon-sm" onclick="app.closeMonitor()" title="Close monitor">×</button>
|
||
</div>
|
||
</div>
|
||
<div class="monitor-resize-handle" id="monitorResizeHandle"></div>
|
||
<div class="monitor-panel-body" id="monitorTabContent">
|
||
<div class="monitor-section">
|
||
<div class="monitor-section-header">
|
||
<span>Tmux Sessions</span>
|
||
</div>
|
||
<div class="monitor-section-body" id="muxSessionsBody">
|
||
<div class="monitor-empty">No mux sessions</div>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
<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>
|
||
<div class="monitor-section" id="backgroundTasksSection" style="display: none;">
|
||
<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>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Subagents Panel (separate from Monitor) -->
|
||
<div class="subagents-panel" id="subagentsPanel">
|
||
<div class="subagents-panel-header" id="subagentsPanelHeader">
|
||
<div class="subagents-panel-title">
|
||
Subagents <span id="subagentCountBadge" class="subagent-badge"></span>
|
||
</div>
|
||
<div class="subagents-panel-actions">
|
||
<button class="btn-toolbar btn-sm btn-danger" onclick="app.clearAllSubagents()" title="Clear all tracked subagents">Clear All</button>
|
||
<button class="btn-icon-sm" onclick="app.toggleSubagentsDetach()" title="Detach panel" id="subagentsDetachBtn">⧉</button>
|
||
<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>
|
||
<div class="subagents-resize-handle" id="subagentsResizeHandle"></div>
|
||
<div class="subagents-panel-body" id="subagentsTabContent">
|
||
<div class="subagent-container">
|
||
<div class="subagent-list" id="subagentList">
|
||
<div class="subagent-empty">No background agents detected</div>
|
||
</div>
|
||
<div class="subagent-detail" id="subagentDetail">
|
||
<div class="subagent-empty">Select an agent to view details</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Session Options Modal (includes Respawn Settings) -->
|
||
<div class="modal" id="sessionOptionsModal">
|
||
<div class="modal-backdrop" onclick="app.closeSessionOptions()"></div>
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Session Options</h3>
|
||
<button class="modal-close" onclick="app.closeSessionOptions()" aria-label="Close session options">×</button>
|
||
</div>
|
||
<!-- Modal Tabs -->
|
||
<div class="modal-tabs">
|
||
<button class="modal-tab-btn active" data-tab="respawn" data-claude-only onclick="app.switchOptionsTab('respawn')">Respawn</button>
|
||
<button class="modal-tab-btn" data-tab="context" onclick="app.switchOptionsTab('context')">Context</button>
|
||
<button class="modal-tab-btn" data-tab="ralph" data-claude-only onclick="app.switchOptionsTab('ralph')">Ralph / Todo</button>
|
||
<button class="modal-tab-btn" data-tab="summary" onclick="app.switchOptionsTab('summary')">Summary</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- Respawn Tab Content -->
|
||
<div class="modal-tab-content" id="respawn-tab">
|
||
<!-- Respawn Settings Section -->
|
||
<div class="session-respawn-section" id="sessionRespawnSection">
|
||
<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>
|
||
|
||
<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>
|
||
|
||
<div class="form-row">
|
||
<label>Duration</label>
|
||
<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>
|
||
|
||
<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>
|
||
|
||
<div class="form-section-header">Cycle Steps</div>
|
||
<div class="form-row">
|
||
<label>1. Update Prompt</label>
|
||
<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>
|
||
</div>
|
||
|
||
<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>
|
||
<label class="checkbox-inline" title="Presses Enter for plan approvals and default question options">
|
||
<input type="checkbox" id="modalRespawnAutoAccept" checked onchange="app.autoSaveRespawnConfig()">
|
||
<span>Auto-accept prompts</span>
|
||
</label>
|
||
</div>
|
||
|
||
<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>
|
||
|
||
<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 -->
|
||
</div>
|
||
</div><!-- End respawn-tab -->
|
||
|
||
<!-- Context Management Tab Content -->
|
||
<div class="modal-tab-content hidden" id="context-tab">
|
||
<div class="form-section-header">Appearance</div>
|
||
<div class="form-row">
|
||
<label>Session Name</label>
|
||
<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>
|
||
<span class="form-hint">Custom name shown in the tab (right-click tab to rename inline)</span>
|
||
</div>
|
||
<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>
|
||
|
||
<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>
|
||
</div>
|
||
<input type="text" id="modalAutoCompactPrompt" value="" placeholder="Focus prompt (optional)" onchange="app.autoSaveAutoCompact()">
|
||
<span class="form-hint">Summarizes context while preserving key information</span>
|
||
</div>
|
||
|
||
<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>
|
||
</div>
|
||
<span class="form-hint">Full context reset at threshold (use higher than compact)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-section-header">File Watching</div>
|
||
<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>
|
||
<span class="form-hint">Auto-popup new images (screenshots, generated files) in this session's directory</span>
|
||
</div>
|
||
|
||
<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>
|
||
</div><!-- End context-tab -->
|
||
|
||
<!-- Ralph Wiggum Tab Content -->
|
||
<div class="modal-tab-content hidden" id="ralph-tab">
|
||
<div class="form-row form-row-switch">
|
||
<label>Enable Tracker</label>
|
||
<label class="switch">
|
||
<input type="checkbox" id="modalRalphEnabled">
|
||
<span class="slider"></span>
|
||
</label>
|
||
<span class="form-hint">Auto-detects loops and todos in Claude output</span>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label>Completion Phrase</label>
|
||
<input type="text" id="modalRalphPhrase" placeholder="e.g., COMPLETE, DONE, FINISHED">
|
||
<span class="form-hint">Phrase Claude outputs when loop is complete (without <promise> tags)</span>
|
||
</div>
|
||
|
||
<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>
|
||
</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 -->
|
||
|
||
<!-- 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 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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">
|
||
<h3>Close Session</h3>
|
||
<button class="modal-close" onclick="app.cancelCloseSession()" aria-label="Cancel close session">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-session-name" id="closeConfirmSessionName"></p>
|
||
</div>
|
||
<div class="close-options">
|
||
<button class="close-option" onclick="app.confirmCloseSession(false)">
|
||
<span class="close-option-title">Remove Tab</span>
|
||
<span class="close-option-desc">Tmux session keeps running in background</span>
|
||
</button>
|
||
<button class="close-option close-option-danger" onclick="app.confirmCloseSession(true)">
|
||
<span class="close-option-title" id="closeConfirmKillTitle">Kill Tmux & Claude Code</span>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- App Settings Modal -->
|
||
<div class="modal" id="appSettingsModal">
|
||
<div class="modal-backdrop" onclick="app.closeAppSettings()"></div>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
<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>
|
||
<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 class="set-row has-field" data-search="voice language">
|
||
<div class="set-row-text">
|
||
<span class="set-row-label">Language</span>
|
||
<span class="set-row-desc">Applies to whichever engine is active.</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. Sent to Claude and Deepgram alike.</span>
|
||
</div>
|
||
<input type="text" id="voiceKeyterms" class="set-input" placeholder="codeman, tmux, respawn, subagent">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
|
||
<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>
|
||
</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">
|
||
<button class="btn-toolbar" onclick="app.closeAppSettings()">Cancel</button>
|
||
<button class="btn-toolbar btn-primary" onclick="app.saveAppSettings()">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
<div class="shortcut-overlay-footer">
|
||
<button class="btn btn-sm" onclick="app.closeShortcutOverlay(); app.showHelp()">Full shortcut reference</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Create Case Modal -->
|
||
<div class="modal" id="createCaseModal">
|
||
<div class="modal-backdrop" onclick="app.closeCreateCaseModal()"></div>
|
||
<div class="modal-content modal-lg">
|
||
<div class="modal-header">
|
||
<h3>Add Case</h3>
|
||
<button class="modal-close" onclick="app.closeCreateCaseModal()" aria-label="Close create case">×</button>
|
||
</div>
|
||
<div class="modal-tabs">
|
||
<button class="modal-tab-btn active" data-tab="case-create">Create New</button>
|
||
<button class="modal-tab-btn" data-tab="case-clone" id="caseCloneTabBtn">Clone Repo</button>
|
||
<button class="modal-tab-btn" data-tab="case-link">Link Existing</button>
|
||
<button class="modal-tab-btn" data-tab="case-remote">Remote</button>
|
||
<button class="modal-tab-btn" data-tab="case-docker">Docker</button>
|
||
<button class="modal-tab-btn" data-tab="case-manage">Manage</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- Create New Tab -->
|
||
<div class="modal-tab-content" id="case-create">
|
||
<div class="form-row">
|
||
<label>Case Name</label>
|
||
<input type="text" id="newCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Letters, numbers, hyphens, underscores only. Created in ~/codeman-cases/</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Description (optional)</label>
|
||
<input type="text" id="newCaseDescription" placeholder="A brief description..." autocomplete="off">
|
||
</div>
|
||
<div class="form-row docker-quick-row">
|
||
<label class="checkbox-row"><input type="checkbox" id="newCaseDocker"> 🐳 Run in an isolated Docker container</label>
|
||
<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>
|
||
</div>
|
||
<details class="advanced-options docker-quick-settings" id="dockerQuickSettings">
|
||
<summary>Container settings (optional, sensible defaults)</summary>
|
||
<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>
|
||
<span class="form-hint">Disk is elastic: storage grows automatically as data flows in (no fixed cap).</span>
|
||
</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>
|
||
</div>
|
||
<!-- 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>
|
||
<!-- Link Existing Tab -->
|
||
<div class="modal-tab-content hidden" id="case-link">
|
||
<div class="form-row">
|
||
<label>Case Name</label>
|
||
<input type="text" id="linkCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Name to identify this case in Codeman</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Folder Path</label>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<!-- 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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<!-- 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>
|
||
</div>
|
||
<!-- 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">
|
||
<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>
|
||
</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>
|
||
<!-- Manage Tab -->
|
||
<div class="modal-tab-content hidden" id="case-manage">
|
||
<div class="case-manage-list" id="caseManageList">
|
||
<!-- Populated by JS -->
|
||
</div>
|
||
<span class="form-hint" style="margin-top: 8px; display: block;">Use arrows to reorder. Changes are saved automatically.</span>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button class="btn-toolbar" onclick="app.closeCreateCaseModal()">Cancel</button>
|
||
<button class="btn-toolbar btn-primary" id="caseModalSubmit" onclick="app.submitCaseModal()">Create</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Ralph Loop Wizard Modal -->
|
||
<div class="modal" id="ralphWizardModal">
|
||
<div class="modal-backdrop"></div>
|
||
<div class="modal-content modal-wizard">
|
||
<div class="modal-header">
|
||
<h3>Start Ralph Loop</h3>
|
||
<div class="modal-header-actions">
|
||
<button class="modal-close" onclick="app.closeRalphWizard()" aria-label="Close Ralph wizard">×</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Wizard Progress Steps -->
|
||
<div class="wizard-progress">
|
||
<div class="wizard-step active" data-step="1">
|
||
<span class="wizard-step-number">1</span>
|
||
<span class="wizard-step-label">Describe</span>
|
||
</div>
|
||
<div class="wizard-step" data-step="2">
|
||
<span class="wizard-step-number">2</span>
|
||
<span class="wizard-step-label">Plan</span>
|
||
</div>
|
||
<div class="wizard-step" data-step="3">
|
||
<span class="wizard-step-number">3</span>
|
||
<span class="wizard-step-label">Launch</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
<!-- Step 1: Task & Completion Setup -->
|
||
<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>
|
||
<div class="form-row">
|
||
<label>What do you want to build?</label>
|
||
<div class="textarea-with-assist">
|
||
<textarea
|
||
id="ralphTaskDescription"
|
||
rows="3"
|
||
placeholder="Build a REST API for user authentication with JWT tokens, login/logout endpoints, and password reset flow."></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="wizard-inline-row">
|
||
<div class="wizard-inline-field">
|
||
<label>Working Directory <span id="existingPlanBadge" class="existing-plan-badge" style="display:none"></span></label>
|
||
<div class="case-select-inline">
|
||
<select id="ralphCaseSelect" class="form-select" onchange="app.onRalphCaseChange()"></select>
|
||
<button class="btn-toolbar btn-sm" onclick="app.showCreateCaseModal()">+</button>
|
||
</div>
|
||
</div>
|
||
<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">
|
||
<!-- 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">
|
||
<div class="plan-loading-content">
|
||
<div class="plan-spinner"></div>
|
||
<div class="plan-loading-text">
|
||
<span class="plan-loading-title" id="planLoadingTitle">Starting Opus 4.5...</span>
|
||
<span class="plan-loading-time" id="planLoadingTime">0s</span>
|
||
</div>
|
||
</div>
|
||
<p class="plan-loading-hint" id="planLoadingHint">Initializing deep reasoning model</p>
|
||
<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>
|
||
<!-- Auto-start toggle visible during generation -->
|
||
<div class="plan-autostart-toggle">
|
||
<label class="plan-autostart-label">
|
||
<input type="checkbox" id="ralphAutoStartStep2" onchange="app.ralphWizardConfig.autoStart = this.checked">
|
||
<span class="plan-autostart-text">Auto-launch when plan completes</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stopped/Error state -->
|
||
<div id="planGenerationError" class="plan-section hidden">
|
||
<div class="plan-stopped-indicator" id="planStoppedIndicator">
|
||
<span class="plan-stopped-icon">■</span>
|
||
<span class="plan-stopped-text">Stopped</span>
|
||
</div>
|
||
<p class="plan-error-msg" id="planErrorMsg"></p>
|
||
<div class="plan-actions">
|
||
<button class="btn-toolbar btn-primary" onclick="app.generatePlan()">Regenerate Plan</button>
|
||
</div>
|
||
<p class="plan-skip-hint">Use <strong>Back</strong> to edit your task, or <strong>Next</strong> to continue without a plan</p>
|
||
</div>
|
||
|
||
<!-- Editor state -->
|
||
<div id="planEditor" class="plan-section hidden">
|
||
<div class="plan-editor-header">
|
||
<span>Implementation Plan <span class="plan-stats" id="planStats"></span></span>
|
||
<div class="plan-editor-actions">
|
||
<div class="plan-option-group">
|
||
<label>Detail:</label>
|
||
<div class="plan-detail-btns">
|
||
<button type="button" class="plan-detail-btn" data-detail="standard" onclick="app.setPlanDetail('standard')" title="Single-pass generation with Opus 4.5">Standard</button>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<button class="btn-toolbar btn-sm" onclick="app.regeneratePlan()">Regenerate</button>
|
||
</div>
|
||
</div>
|
||
<div id="planItemsList" class="plan-items-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 3: Preview & Launch -->
|
||
<div class="wizard-page hidden" id="ralphWizardStep3">
|
||
<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>
|
||
<div class="config-summary-row" style="display: none;">
|
||
<span class="config-label">Plan:</span>
|
||
<span id="summaryPlan">-</span>
|
||
</div>
|
||
</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">
|
||
<input type="checkbox" id="ralphEnableRespawn">
|
||
<span class="slider"></span>
|
||
</label>
|
||
<span class="form-hint">Auto-restart sessions when context fills up (usually not needed)</span>
|
||
</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>
|
||
|
||
|
||
<!-- 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>
|
||
<button class="modal-close" onclick="app.closeKillAllModal()" aria-label="Close kill all modal">×</button>
|
||
</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>
|
||
<span class="close-option-desc">Tmux sessions keep running in background</span>
|
||
</button>
|
||
<button class="close-option close-option-danger" onclick="app.confirmKillAll(true)">
|
||
<span class="close-option-title">Kill All Claude + Tmux</span>
|
||
<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>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- 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>
|
||
|
||
|
||
<!-- 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>
|
||
<button class="modal-close" onclick="app.closeTokenStats()" aria-label="Close token statistics">×</button>
|
||
</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>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- 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">
|
||
<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>
|
||
<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>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
|
||
<!-- SVG Overlay for Subagent Connection Lines -->
|
||
<svg id="connectionLines" class="connection-lines-svg">
|
||
<!-- Lines drawn dynamically -->
|
||
</svg>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Approvals Inbox drawer (populated by approvals-ui.js; opened from the header bell) -->
|
||
<!-- 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>
|
||
<div class="readmymind-alternates" id="readMyMindAlternates" data-i18n-skip style="display:none"></div>
|
||
</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>
|
||
|
||
<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>
|
||
|
||
<script defer src="constants.js"></script>
|
||
<script defer src="i18n.js"></script>
|
||
<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>
|
||
<script defer src="input-cjk.js"></script>
|
||
<!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
|
||
<script defer src="sanitize-html.js"></script>
|
||
<script defer src="app.js"></script>
|
||
<script defer src="terminal-ui.js"></script>
|
||
<script defer src="respawn-ui.js"></script>
|
||
<script defer src="ralph-panel.js"></script>
|
||
<script defer src="orchestrator-panel.js"></script>
|
||
<script defer src="cron-ui.js"></script>
|
||
<script defer src="settings-ui.js"></script>
|
||
<script defer src="panels-ui.js"></script>
|
||
<script defer src="readmymind-ui.js"></script>
|
||
<script defer src="ultracode-panel.js"></script>
|
||
<script defer src="approvals-ui.js"></script>
|
||
<script defer src="admin-ui.js"></script>
|
||
<script defer src="session-ui.js"></script>
|
||
<script defer src="webview-tabs.js"></script>
|
||
<script defer src="mobile-overview.js"></script>
|
||
<script defer src="home-sessions.js"></script>
|
||
<script defer src="entrance-animations.js"></script>
|
||
<script defer src="ralph-wizard.js"></script>
|
||
<script defer src="api-client.js"></script>
|
||
<script defer src="subagent-windows.js"></script>
|
||
<script defer src="ultracode-windows.js"></script>
|
||
<script defer src="image-input.js"></script>
|
||
</body>
|
||
</html>
|