81 lines
3.1 KiB
TypeScript
81 lines
3.1 KiB
TypeScript
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<string, string> = { idle: `${dim}◌${reset}`, active: `${cyan}◯${reset}`, dirty: `${yellow}◎${reset}`, saved: `${green}●${reset}` }
|
|
const branchColors: Record<string, string> = { 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}`)
|
|
}
|