import { homedir } from "os" import * as git from "../git.ts" import * as vm from "../vm.ts" import * as state from "../state.ts" import { reset, dim, bold, green, yellow, cyan, white } from "../fmt.ts" export async function action(opts: { json?: boolean }) { const root = await git.repoRoot() const st = await state.load(root) const sessions = Object.values(st.sessions) // Discover prompts from Claude history for sessions that lack one const needsPrompt = sessions.filter(s => !s.prompt) if (needsPrompt.length > 0 && (await vm.status()) === "running") { try { const result = await vm.exec(homedir() + "/.sandlot", "cat /home/ubuntu/.claude/history.jsonl 2>/dev/null") if (result.exitCode === 0 && result.stdout) { const entries = result.stdout.split("\n").filter(Boolean).map(line => { try { return JSON.parse(line) } catch { return null } }).filter(Boolean) for (const s of needsPrompt) { const cPath = vm.containerPath(s.worktree) const match = entries.find((e: any) => e.project === cPath) if (match?.display) { s.prompt = match.display } } } } catch {} } if (opts.json) { console.log(JSON.stringify(sessions, null, 2)) return } if (sessions.length === 0) { console.log("◆ No active sessions.") return } // Determine status for each session in parallel const statusEntries = await Promise.all( sessions.map(async (s): Promise<[string, string]> => { if (await vm.isClaudeActive(s.worktree, s.branch)) return [s.branch, "active"] const dirty = await git.isDirty(s.worktree) if (dirty) return [s.branch, "dirty"] const commits = await git.hasNewCommits(s.worktree) return [s.branch, commits ? "saved" : "idle"] }) ) const statuses = Object.fromEntries(statusEntries) if (opts.json) { const withStatus = sessions.map(s => ({ ...s, status: statuses[s.branch] })) console.log(JSON.stringify(withStatus, null, 2)) return } const icons: Record = { idle: `${dim}◌${reset}`, active: `${cyan}◯${reset}`, dirty: `${yellow}◎${reset}`, saved: `${green}●${reset}` } const branchColors: Record = { idle: dim, active: cyan, dirty: yellow, saved: green } const branchWidth = Math.max(6, ...sessions.map((s) => s.branch.length)) const cols = process.stdout.columns || 80 const prefixWidth = branchWidth + 4 console.log(` ${dim}${"BRANCH".padEnd(branchWidth)} PROMPT${reset}`) for (const s of sessions) { const prompt = (s.prompt ?? "").replace(/\n/g, " ") const status = statuses[s.branch] const icon = icons[status] const bc = branchColors[status] const maxPrompt = cols - prefixWidth const truncated = maxPrompt > 3 && prompt.length > maxPrompt ? prompt.slice(0, maxPrompt - 3) + "..." : prompt console.log(`${icon} ${bc}${s.branch.padEnd(branchWidth)}${reset} ${dim}${truncated}${reset}`) } console.log(`\n${dim}◌ idle${reset} · ${cyan}◯ active${reset} · ${yellow}◎ unsaved${reset} · ${green}● saved${reset}`) }