Compare commits
2 Commits
3438dddd5c
...
003c42bc03
| Author | SHA1 | Date | |
|---|---|---|---|
| 003c42bc03 | |||
| c06b0284db |
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@because/sandlot",
|
"name": "@because/sandlot",
|
||||||
"version": "0.0.23",
|
"version": "0.0.24",
|
||||||
"description": "Sandboxed, branch-based development with Claude",
|
"description": "Sandboxed, branch-based development with Claude",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ export async function action(opts: { json?: boolean }) {
|
||||||
// Determine status for each session in parallel
|
// Determine status for each session in parallel
|
||||||
const statusEntries = await Promise.all(
|
const statusEntries = await Promise.all(
|
||||||
sessions.map(async (s): Promise<[string, string]> => {
|
sessions.map(async (s): Promise<[string, string]> => {
|
||||||
|
if (!(await git.isValidWorktree(s.worktree))) return [s.branch, "stale"]
|
||||||
if (await vm.isClaudeActive(s.worktree, s.branch)) return [s.branch, "active"]
|
if (await vm.isClaudeActive(s.worktree, s.branch)) return [s.branch, "active"]
|
||||||
const dirty = await git.isDirty(s.worktree)
|
const dirty = await git.isDirty(s.worktree)
|
||||||
if (dirty) return [s.branch, "dirty"]
|
if (dirty) return [s.branch, "dirty"]
|
||||||
|
|
@ -61,8 +62,8 @@ export async function action(opts: { json?: boolean }) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const icons: Record<string, string> = { idle: `${dim}◯${reset}`, active: `${cyan}◎${reset}`, dirty: `${yellow}◐${reset}`, saved: `${green}●${reset}` }
|
const icons: Record<string, string> = { idle: `${dim}◯${reset}`, active: `${cyan}◎${reset}`, dirty: `${yellow}◐${reset}`, saved: `${green}●${reset}`, stale: `${red}✖${reset}` }
|
||||||
const branchColors: Record<string, string> = { idle: dim, active: cyan, dirty: yellow, saved: green }
|
const branchColors: Record<string, string> = { idle: dim, active: cyan, dirty: yellow, saved: green, stale: red }
|
||||||
const branchWidth = Math.max(6, ...sessions.map((s) => s.branch.length))
|
const branchWidth = Math.max(6, ...sessions.map((s) => s.branch.length))
|
||||||
const cols = process.stdout.columns || 80
|
const cols = process.stdout.columns || 80
|
||||||
const prefixWidth = branchWidth + 4
|
const prefixWidth = branchWidth + 4
|
||||||
|
|
@ -70,7 +71,7 @@ export async function action(opts: { json?: boolean }) {
|
||||||
console.log(` ${dim}${"BRANCH".padEnd(branchWidth)} PROMPT${reset}`)
|
console.log(` ${dim}${"BRANCH".padEnd(branchWidth)} PROMPT${reset}`)
|
||||||
|
|
||||||
for (const s of sessions) {
|
for (const s of sessions) {
|
||||||
const prompt = (s.prompt ?? "").split("\n")[0]
|
const prompt = status === "stale" ? "broken worktree — close with -f to clean up" : (s.prompt ?? "").split("\n")[0]
|
||||||
const status = statuses[s.branch]
|
const status = statuses[s.branch]
|
||||||
const icon = icons[status]
|
const icon = icons[status]
|
||||||
const bc = branchColors[status]
|
const bc = branchColors[status]
|
||||||
|
|
@ -79,7 +80,9 @@ export async function action(opts: { json?: boolean }) {
|
||||||
console.log(`${icon} ${bc}${s.branch.padEnd(branchWidth)}${reset} ${dim}${truncated}${reset}`)
|
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}`)
|
const hasStale = Object.values(statuses).includes("stale")
|
||||||
|
const legend = `${dim}◯ idle${reset} · ${cyan}◎ active${reset} · ${yellow}◐ unsaved${reset} · ${green}● saved${reset}`
|
||||||
|
console.log(`\n${legend}${hasStale ? ` · ${red}✖ stale${reset} (run ${dim}sandlot close -f <branch>${reset} to clean up)` : ""}`)
|
||||||
|
|
||||||
if ((await vm.status()) !== "running") {
|
if ((await vm.status()) !== "running") {
|
||||||
console.log(`\n${red}VM is not running.${reset}`)
|
console.log(`\n${red}VM is not running.${reset}`)
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,12 @@ export async function rebaseAbort(cwd: string): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if a worktree has uncommitted changes. */
|
/** Check if a worktree has uncommitted changes. */
|
||||||
|
/** Check if a worktree path is a valid git directory. */
|
||||||
|
export async function isValidWorktree(worktreePath: string): Promise<boolean> {
|
||||||
|
const result = await $`git -C ${worktreePath} rev-parse --git-dir`.nothrow().quiet()
|
||||||
|
return result.exitCode === 0
|
||||||
|
}
|
||||||
|
|
||||||
export async function isDirty(worktreePath: string): Promise<boolean> {
|
export async function isDirty(worktreePath: string): Promise<boolean> {
|
||||||
const result = await $`git -C ${worktreePath} status --porcelain`.nothrow().quiet()
|
const result = await $`git -C ${worktreePath} status --porcelain`.nothrow().quiet()
|
||||||
if (result.exitCode !== 0) return false
|
if (result.exitCode !== 0) return false
|
||||||
|
|
@ -227,7 +233,8 @@ export async function fileDiff(ref1: string, ref2: string, file: string, cwd: st
|
||||||
|
|
||||||
/** Check if a branch has commits beyond main. */
|
/** Check if a branch has commits beyond main. */
|
||||||
export async function hasNewCommits(worktreePath: string): Promise<boolean> {
|
export async function hasNewCommits(worktreePath: string): Promise<boolean> {
|
||||||
const main = await mainBranch(worktreePath)
|
let main: string
|
||||||
|
try { main = await mainBranch(worktreePath) } catch { return false }
|
||||||
const result = await $`git -C ${worktreePath} rev-list ${main}..HEAD --count`.nothrow().quiet()
|
const result = await $`git -C ${worktreePath} rev-list ${main}..HEAD --count`.nothrow().quiet()
|
||||||
if (result.exitCode !== 0) return false
|
if (result.exitCode !== 0) return false
|
||||||
return parseInt(result.text().trim(), 10) > 0
|
return parseInt(result.text().trim(), 10) > 0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user