import { json } from '@sveltejs/kit'; import { execSync } from 'child_process'; export async function GET({ url }) { try { const limit = url.searchParams.get('limit') || '50'; const output = execSync(`hermes sessions list --limit ${limit} 2>/dev/null`, { timeout: 5000, encoding: 'utf-8' }); const sessions = parseSessions(output); return json(sessions); } catch { return json([]); } } /** * Parse the plain-text output of `hermes sessions list`. * * Expected format (column-aligned with spaces): * Title Preview Last Active ID * ────────────────────────────────────────────────────────────────────────────────────────────────────────────── * — create a way to connect to hermes via 2m ago 20260522_045714_48480d * Self-Improvement and Token Eff find me good self improvement skills. 45m ago 20260522_045210_2381af * * The ID column is always the last column and matches: YYYYMMDD_HHMMSS_[hex] * The "Last Active" column is second-to-last. */ function parseSessions(output: string) { const lines = output.split('\n').filter((l) => l.trim()); const sessions: { id: string; title: string; preview: string; timestamp: number }[] = []; for (const line of lines) { // Skip header and separator lines if (line.startsWith('Title') || line.startsWith('──')) continue; // Match the session ID at the end: YYYYMMDD_HHMMSS_[hex] const idMatch = line.match(/(\d{8}_\d{6}_[0-9a-f]+)\s*$/); if (!idMatch) continue; const id = idMatch[1]; const beforeId = line.slice(0, line.lastIndexOf(id)).trimEnd(); // Match the "Last Active" column (relative time like "2m ago", "1h ago", "3d ago") const timeMatch = beforeId.match(/(\d+[smhdw]\s+ago)\s*$/); let timestamp = Date.now(); let beforeTime = beforeId; if (timeMatch) { const timeStr = timeMatch[1]; beforeTime = beforeId.slice(0, beforeId.lastIndexOf(timeStr)).trimEnd(); timestamp = parseRelativeTime(timeStr); } // Everything before the time column is Title + Preview // Title is typically up to ~32 chars, Preview fills the rest // We'll split on the first long gap of spaces const parts = beforeTime.split(/\s{3,}/); let title = parts[0] || 'Untitled'; let preview = parts.slice(1).join(' ') || ''; // Clean up title (replace dash-only titles) if (/^[—\-]+$/.test(title)) title = 'Untitled'; sessions.push({ id, title, preview, timestamp }); } return sessions; } function parseRelativeTime(str: string): number { const now = Date.now(); const match = str.match(/^(\d+)\s*([smhdw])\s+ago$/); if (!match) return now; const value = parseInt(match[1]); const unit = match[2]; const multipliers: Record = { s: 1000, m: 60 * 1000, h: 60 * 60 * 1000, d: 24 * 60 * 60 * 1000, w: 7 * 24 * 60 * 60 * 1000 }; return now - value * (multipliers[unit] || 0); }