UNPKG

@jovan158/vantage

Version:

See and control what your coding agent does

107 lines (106 loc) • 4.35 kB
// `vantage watch`: the live view, meant for a second terminal next to the one // Claude Code runs in. import path from "node:path"; import { EventTail, sessionEventsPath } from "../events.js"; import { renderLive, renderOverview, findWatchTarget } from "../watch.js"; import { findSession, sessionRunning, SessionList } from "../home.js"; export async function cmdWatch(argv) { const cwd = process.cwd(); const pinned = argv[0]; const interval = 500; // Renders into THIS terminal only — the agent's own terminal is never // touched, which is the whole point of watching from a second pane. const draw = (body) => { process.stdout.write("\x1b[H\x1b[J" + body + "\n"); // home, clear to end }; const waiting = "waiting for a session… (start one with `vantage run claude`)"; const key = (r) => `${path.resolve(r.cwd)}|${r.sessionId}`; // Logs are followed, not re-read: each tick parses only what was appended. const tails = new Map(); const eventsOf = (ref) => { let tail = tails.get(key(ref)); if (!tail) tails.set(key(ref), (tail = new EventTail(sessionEventsPath(ref.cwd, ref.sessionId)))); return tail.read(); }; // Sessions seen ended stay ended; no need to look at them again. const ended = new Set(); const list = new SessionList(cwd); const running = () => list.get().filter((r) => { if (ended.has(key(r))) return false; if (sessionRunning(r)) return true; ended.add(key(r)); return false; }); // The session to show when none runs changes only when the list does. let fallbackFor = null; let fallback = null; const lastSession = () => { const refs = list.get(); if (refs !== fallbackFor) { fallbackFor = refs; fallback = findWatchTarget(cwd); } return fallback; }; const detail = (ref, events = eventsOf(ref)) => { if (events.length === 0) return null; return renderLive(events, { sessionId: ref.sessionId, project: path.resolve(ref.cwd) === path.resolve(cwd) ? undefined : ref.cwd, color: process.stdout.isTTY ?? false, width: process.stdout.columns, }); }; return await new Promise((resolve) => { // The first tick runs right away and may already stop (a pinned session // that has ended), before the interval exists. let timer; let pinnedRef = null; let stopped = false; const stop = () => { stopped = true; if (timer) clearInterval(timer); process.stdout.write("\n"); resolve(0); }; process.on("SIGINT", stop); const tick = () => { // A pinned session: found here or anywhere this machine recorded it; // watching ends with it. if (pinned) { pinnedRef ??= findSession(pinned, cwd); if (!pinnedRef) return draw(`no session ${pinned} found — see \`vantage sessions\` or \`vantage search\``); const events = eventsOf(pinnedRef); const frame = detail(pinnedRef, events); if (frame) draw(frame); // Ended, or its vantage process is gone (crashed without an end). if (events.some((e) => e.type === "session_end") || !sessionRunning(pinnedRef)) stop(); return; } // Otherwise: several running sessions side by side, one running // session in detail, or the last one when none runs. const live = running(); if (live.length >= 2) { const items = live.map((ref) => ({ ref, events: eventsOf(ref) })); return draw(renderOverview(items, { color: process.stdout.isTTY ?? false, width: process.stdout.columns })); } const target = live[0] ?? lastSession(); if (!target) return draw(waiting); const frame = detail(target); if (frame) draw(frame); }; tick(); if (!stopped) timer = setInterval(tick, interval); }); }