chore: bump version to 0.1557

Lifecycle log window: draggable, resizable, wider (900px), hidden on mobile.
Fixed display toggle (block vs empty string).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-02-19 19:55:04 +01:00
co-authored by Claude Opus 4.6
parent 383ed5e5a0
commit cf0d9e7d83
11 changed files with 117 additions and 60 deletions
+2 -1
View File
@@ -35,7 +35,7 @@ When user says "COM":
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`)
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
**Version**: 0.1556 (must match `package.json` for npm publish)
**Version**: 0.1557 (must match `package.json` for npm publish)
## Project Overview
@@ -120,6 +120,7 @@ journalctl --user -u claudeman-web -f
| `src/bash-tool-parser.ts` | Parses Claude's bash tool invocations from output |
| `src/transcript-watcher.ts` | Watches Claude's transcript files for changes |
| `src/hooks-config.ts` | Manages `.claude/settings.local.json` hook configuration |
| `src/session-lifecycle-log.ts` | Append-only JSONL audit log at `~/.claudeman/session-lifecycle.jsonl` |
| `src/image-watcher.ts` | Watches for image file creation (screenshots, etc.) |
| `src/file-stream-manager.ts` | Manages `tail -f` processes for live log viewing |
| `src/plan-orchestrator.ts` | Multi-agent plan generation with research and planning phases |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claudeman",
"version": "0.1556",
"version": "0.1557",
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
"type": "module",
"main": "dist/index.js",
+41 -24
View File
@@ -22,6 +22,7 @@ const PROJECT_ROOT = join(__dirname, '..');
const PUBLIC_DIR = join(PROJECT_ROOT, 'src', 'web', 'public');
const PORT = 3199;
const VIEWPORT = { width: 1280, height: 720 };
const DEVICE_SCALE_FACTOR = 1;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -117,10 +118,10 @@ function makeSession(id, name, mode, status, extra = {}) {
niceValue: 10,
color: 'default',
flickerFilterEnabled: false,
cliVersion: '2.1.14',
cliModel: 'Opus 4.5',
cliVersion: '2.1.47',
cliModel: 'Opus 4.6',
cliAccountType: 'Claude Max',
cliLatestVersion: '2.1.14',
cliLatestVersion: '2.1.47',
messageCount: mode === 'claude' ? 15 : 0,
isWorking: status === 'busy',
lastPromptTime: Date.now() - 30000,
@@ -298,7 +299,7 @@ function buildInitPayload(sessions, subagents = []) {
}
return {
version: '0.1540',
version: '0.1556',
sessions,
scheduledRuns: [],
respawnStatus,
@@ -333,8 +334,8 @@ const DIM = '\x1b[2m';
// Claude Code init banner (matching the overview screenshot)
const TERMINAL_INIT = [
'',
` ${RED}████${RST} ${BOLD}Claude Code${RST} v2.1.14`,
` ${RED}████${RST} ${GRY}Opus 4.5${RST} ${DIM}·${RST} ${GRY}Claude Max${RST}`,
` ${RED}████${RST} ${BOLD}Claude Code${RST} v2.1.47`,
` ${RED}████${RST} ${GRY}Opus 4.6${RST} ${DIM}·${RST} ${GRY}Claude Max${RST}`,
` ${RED}██${RST} ${RED}██${RST} ${GRY}~/claudeman-cases/testcase${RST}`,
'',
'',
@@ -343,7 +344,7 @@ const TERMINAL_INIT = [
'',
`${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`,
` ${BOLD}0 tokens${RST}`,
` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`,
` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`,
].join('\r\n');
// Claude working output (for multi-session-dashboard / subagent-spawn)
@@ -380,7 +381,7 @@ const TERMINAL_WORKING = [
`────────────────────────────────────────────────────────────────────────────────────────────────────────────────`,
` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`,
` ${BOLD}28163 tokens${RST}`,
` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`,
` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`,
].join('\r\n');
// Ralph terminal content (matching ralph-tracker screenshot)
@@ -412,7 +413,7 @@ const TERMINAL_RALPH = [
`────────────────────────────────────────────────────────────────────────────────────────────────────────────────`,
` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`,
` ${BOLD}37267 tokens${RST}`,
` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`,
` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`,
].join('\r\n');
// Subagent window content (tool call activity)
@@ -470,7 +471,7 @@ const TERMINAL_SUBAGENT = [
'',
'',
` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`,
` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`,
` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`,
].join('\r\n');
// ─── Route Interceptors ──────────────────────────────────────────────────────
@@ -589,7 +590,8 @@ async function setupRoutes(page, initPayload, terminalContent) {
*/
async function injectState(page, initPayload, terminalContent, activeSessionId) {
// Wait for app to be ready
await page.waitForFunction(() => window.app && window.app.terminal, { timeout: 10000 });
await page.waitForFunction(() => window.app && window.app.terminal, { timeout: 15000 });
await sleep(1000);
// Cancel the SSE fallback timer and inject state directly
await page.evaluate((payload) => {
@@ -603,7 +605,7 @@ async function injectState(page, initPayload, terminalContent, activeSessionId)
app.handleInit(payload);
}, initPayload);
await sleep(500);
await sleep(1500);
// Select the target session (this triggers terminal fetch via our mocked route)
if (activeSessionId) {
@@ -611,7 +613,7 @@ async function injectState(page, initPayload, terminalContent, activeSessionId)
window.app.activeSessionId = null; // Force re-select
window.app.selectSession(sid);
}, activeSessionId);
await sleep(800);
await sleep(2000);
}
// Write terminal content directly as backup (in case fetch didn't work)
@@ -630,11 +632,25 @@ async function injectState(page, initPayload, terminalContent, activeSessionId)
}
}, terminalContent);
await sleep(1000);
// Clean up UI elements that look wrong in screenshots
await page.evaluate(() => {
// Hide connection indicator (SSE is blocked so it shows "Reconnecting...")
// Kill SSE reconnection entirely and hide the connection indicator
const app2 = window.app;
if (app2) {
// Stop reconnection timers
if (app2.sseReconnectTimeout) clearTimeout(app2.sseReconnectTimeout);
if (app2.eventSource) { app2.eventSource.close(); app2.eventSource = null; }
app2._connectionStatus = 'connected';
// Monkey-patch so it never re-shows
app2._updateConnectionIndicator = () => {};
app2.connectSSE = () => {};
app2.setConnectionStatus = () => {};
}
// Remove the indicator element from the DOM entirely
const indicator = document.getElementById('connectionIndicator');
if (indicator) indicator.style.display = 'none';
if (indicator) indicator.remove();
// Hide respawn banner (shows "idle" state which isn't needed in screenshots)
const respawnBanner = document.getElementById('respawnBanner');
@@ -676,7 +692,8 @@ async function injectState(page, initPayload, terminalContent, activeSessionId)
}
});
await sleep(300);
// Final settle — let xterm.js WebGL renderer, fonts, and layout stabilize
await sleep(2000);
}
// ─── Screenshot Scenarios ────────────────────────────────────────────────────
@@ -701,7 +718,7 @@ async function captureOverview(page) {
app._renderMuxSessionsImmediate();
}
}, MUX_SESSIONS);
await sleep(500);
await sleep(1500);
await page.screenshot({
path: join(PROJECT_ROOT, 'docs', 'images', 'claude-overview.png'),
@@ -733,9 +750,9 @@ async function captureSubagentSpawn(page) {
app.openSubagentWindow('agent-001');
app.openSubagentWindow('agent-002');
}, SUBAGENT_ACTIVITY);
await sleep(800);
await sleep(2000);
// Position windows nicely (matching existing screenshot layout)
// Position windows nicely for 1280x720 viewport
await page.evaluate(() => {
const app = window.app;
if (!app) return;
@@ -755,7 +772,7 @@ async function captureSubagentSpawn(page) {
w2.style.height = '320px';
}
});
await sleep(500);
await sleep(1500);
await page.screenshot({
path: join(PROJECT_ROOT, 'docs', 'images', 'subagent-spawn.png'),
@@ -789,7 +806,7 @@ async function captureRalphTracker(page) {
app._renderRalphStatePanelImmediate();
}
}, RALPH_SESSION_ID);
await sleep(500);
await sleep(1500);
await page.screenshot({
path: join(PROJECT_ROOT, 'docs', 'images', 'ralph-tracker-8tasks-44percent.png'),
@@ -834,7 +851,7 @@ async function captureMultiSessionMonitor(page) {
app._renderMuxSessionsImmediate();
}
}, MUX_SESSIONS);
await sleep(500);
await sleep(1500);
await page.screenshot({
path: join(PROJECT_ROOT, 'docs', 'screenshots', 'multi-session-monitor.png'),
@@ -874,9 +891,9 @@ async function main() {
captureMultiSessionDashboard,
captureMultiSessionMonitor,
]) {
const context = await browser.newContext({ viewport: VIEWPORT });
const context = await browser.newContext({ viewport: VIEWPORT, deviceScaleFactor: DEVICE_SCALE_FACTOR });
const page = await context.newPage();
page.setDefaultTimeout(15000);
page.setDefaultTimeout(30000);
try {
await scenario(page);
+41 -2
View File
@@ -9909,12 +9909,51 @@ class ClaudemanApp {
// ========== Session Lifecycle Log ==========
openLifecycleLog() {
document.getElementById('lifecycleModal').classList.add('active');
const win = document.getElementById('lifecycleWindow');
win.style.display = 'block';
// Reset transform so it appears centered initially
if (!win._dragInitialized) {
win.style.left = '50%';
win.style.transform = 'translateX(-50%)';
this._initLifecycleDrag(win);
win._dragInitialized = true;
}
this.loadLifecycleLog();
}
closeLifecycleLog() {
document.getElementById('lifecycleModal').classList.remove('active');
document.getElementById('lifecycleWindow').style.display = 'none';
}
_initLifecycleDrag(win) {
const header = document.getElementById('lifecycleWindowHeader');
let isDragging = false;
let startX, startY, startLeft, startTop;
header.addEventListener('mousedown', (e) => {
if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
isDragging = true;
// Clear transform so left/top work in absolute pixels
const rect = win.getBoundingClientRect();
win.style.transform = 'none';
win.style.left = rect.left + 'px';
win.style.top = rect.top + 'px';
startX = e.clientX;
startY = e.clientY;
startLeft = rect.left;
startTop = rect.top;
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
win.style.left = (startLeft + e.clientX - startX) + 'px';
win.style.top = (startTop + e.clientY - startY) + 'px';
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
}
async loadLifecycleLog() {
+29 -30
View File
@@ -8,8 +8,8 @@
<meta name="google" content="notranslate">
<title>Claudeman</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
<link rel="stylesheet" href="styles.css?v=0.1536">
<link rel="stylesheet" href="mobile.css?v=0.1536" media="(max-width: 1023px)">
<link rel="stylesheet" href="styles.css?v=0.1556">
<link rel="stylesheet" href="mobile.css?v=0.1556" 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>
@@ -84,7 +84,7 @@
<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" onclick="app.openLifecycleLog()" title="Session Lifecycle Log" aria-label="Open session lifecycle log" style="font-size:14px;opacity:0.7">&#x1F4CB;</button>
<button class="btn-icon-header btn-lifecycle-log" 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" title="Total tokens across all sessions">0 tokens</div>
</div>
@@ -1501,17 +1501,13 @@
</div>
</div>
<!-- Session Lifecycle Audit Log Modal -->
<div class="modal" id="lifecycleModal">
<div class="modal-backdrop" onclick="app.closeLifecycleLog()"></div>
<div class="modal-content modal-lg">
<div class="modal-header">
<h3>Session Lifecycle Log</h3>
<button class="modal-close" onclick="app.closeLifecycleLog()" aria-label="Close lifecycle log">&times;</button>
</div>
<div class="modal-body" style="max-height:70vh;overflow:auto;padding:8px">
<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap">
<select id="lifecycleFilterEvent" style="background:#1a1a2e;color:#e0e0e0;border:1px solid #333;border-radius:4px;padding:4px 8px;font-size:12px">
<!-- 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>
@@ -1523,24 +1519,27 @@
<option value="server_started">Server Started</option>
<option value="server_stopped">Server Stopped</option>
</select>
<input id="lifecycleFilterSession" type="text" placeholder="Filter by session ID..." style="background:#1a1a2e;color:#e0e0e0;border:1px solid #333;border-radius:4px;padding:4px 8px;font-size:12px;flex:1;min-width:120px">
<button class="btn btn-sm btn-secondary" onclick="app.loadLifecycleLog()" style="font-size:12px;padding:4px 10px">Refresh</button>
<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>
<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">
<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>
<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">&times;</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 -->
@@ -1548,6 +1547,6 @@
<!-- Lines drawn dynamically -->
</svg>
<script defer src="app.js?v=0.1536"></script>
<script defer src="app.js?v=0.1556"></script>
</body>
</html>
+3 -2
View File
@@ -308,8 +308,9 @@ html.mobile-init .file-browser-panel {
height: 12px;
}
/* Hide header settings gear on mobile - moved to toolbar */
.btn-icon-header.btn-settings {
/* Hide header settings gear and lifecycle log on mobile - settings moved to toolbar */
.btn-icon-header.btn-settings,
.btn-icon-header.btn-lifecycle-log {
display: none !important;
}