Compare commits
No commits in common. "b46511efe351fa75910c25fa40921dc08148dfa7" and "cfb9ed6172b049f1605c5da6b80f8921c4e61e15" have entirely different histories.
b46511efe3
...
cfb9ed6172
4
bun.lock
4
bun.lock
|
|
@ -5,7 +5,7 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "sandlot",
|
"name": "sandlot",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^14.0.3",
|
"commander": "^13.1.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.9",
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.9", "https://npm.nose.space/bun-types/-/bun-types-1.3.9.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
"bun-types": ["bun-types@1.3.9", "https://npm.nose.space/bun-types/-/bun-types-1.3.9.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||||
|
|
||||||
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
"commander": ["commander@13.1.0", "https://npm.nose.space/commander/-/commander-13.1.0.tgz", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@5.26.5", "https://npm.nose.space/undici-types/-/undici-types-5.26.5.tgz", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
"undici-types": ["undici-types@5.26.5", "https://npm.nose.space/undici-types/-/undici-types-5.26.5.tgz", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sandlot": "./src/cli.ts"
|
"sandlot": "./src/cli.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^14.0.3"
|
"commander": "^13.1.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test:markdown": "bun src/test-markdown.ts"
|
"test:markdown": "bun src/test-markdown.ts"
|
||||||
|
|
|
||||||
142
src/cli.ts
142
src/cli.ts
|
|
@ -1,7 +1,6 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { Command, Option } from "commander"
|
import { Command } from "commander"
|
||||||
import { yellow, reset } from "./fmt.ts"
|
|
||||||
import * as git from "./git.ts"
|
import * as git from "./git.ts"
|
||||||
import * as state from "./state.ts"
|
import * as state from "./state.ts"
|
||||||
import { action as newAction } from "./commands/new.ts"
|
import { action as newAction } from "./commands/new.ts"
|
||||||
|
|
@ -26,26 +25,9 @@ const pkg = await Bun.file(new URL("../package.json", import.meta.url)).json()
|
||||||
|
|
||||||
const program = new Command()
|
const program = new Command()
|
||||||
|
|
||||||
program
|
program.name("sandlot").description("Branch-based development with git worktrees and Apple Container").version(pkg.version)
|
||||||
.name("sandlot")
|
|
||||||
.description("Sandboxed development with Claude.")
|
|
||||||
.configureHelp({ styleTitle: (str) => `${yellow}${str}${reset}` })
|
|
||||||
.helpOption(false)
|
|
||||||
.addOption(new Option("-h, --help").hideHelp())
|
|
||||||
.on("option:help", () => program.help())
|
|
||||||
.addOption(new Option("-V, --version").hideHelp())
|
|
||||||
.on("option:version", () => {
|
|
||||||
console.log(pkg.version)
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
// ── Sessions ────────────────────────────────────────────────────────
|
// ── sandlot new ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
program
|
|
||||||
.command("list")
|
|
||||||
.description("Show all active sessions")
|
|
||||||
.option("--json", "Output as JSON")
|
|
||||||
.action(listAction)
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("new")
|
.command("new")
|
||||||
|
|
@ -56,15 +38,44 @@ program
|
||||||
.description("Create a new session and launch Claude")
|
.description("Create a new session and launch Claude")
|
||||||
.action(newAction)
|
.action(newAction)
|
||||||
|
|
||||||
|
// ── sandlot list ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("list")
|
||||||
|
.description("Show all active sessions (◌ idle · ◯ working · ◎ unsaved · ● saved)")
|
||||||
|
.option("--json", "Output as JSON")
|
||||||
|
.action(listAction)
|
||||||
|
|
||||||
|
// ── sandlot open ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("open")
|
.command("open")
|
||||||
.argument("<branch>", "branch name")
|
.argument("<branch>", "branch name")
|
||||||
.argument("[prompt]", "initial prompt for Claude")
|
.argument("[prompt]", "initial prompt for Claude")
|
||||||
.option("-p, --print <prompt>", "run Claude in non-interactive mode with -p")
|
.option("-p, --print <prompt>", "run Claude in non-interactive mode with -p")
|
||||||
.option("-n, --no-save", "skip auto-save after Claude exits")
|
.option("-n, --no-save", "skip auto-save after Claude exits")
|
||||||
.description("Open an existing Claude session")
|
.description("Re-enter an existing session")
|
||||||
.action(openAction)
|
.action(openAction)
|
||||||
|
|
||||||
|
// ── sandlot review ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("review")
|
||||||
|
.argument("<branch>", "branch name")
|
||||||
|
.option("-p, --print", "print the review to stdout instead of launching interactive mode")
|
||||||
|
.description("Launch an interactive grumpy code review for a branch")
|
||||||
|
.action(reviewAction)
|
||||||
|
|
||||||
|
// ── sandlot shell ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("shell")
|
||||||
|
.argument("[branch]", "branch name (omit for a plain VM shell)")
|
||||||
|
.description("Open a shell in the VM (at the session's worktree if branch given)")
|
||||||
|
.action(shellAction)
|
||||||
|
|
||||||
|
// ── sandlot close ────────────────────────────────────────────────────
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("close")
|
.command("close")
|
||||||
.argument("<branch>", "branch name")
|
.argument("<branch>", "branch name")
|
||||||
|
|
@ -79,27 +90,31 @@ program
|
||||||
.description("Remove a session (alias for close)")
|
.description("Remove a session (alias for close)")
|
||||||
.action((branch: string, opts: { force?: boolean }) => closeAction(branch, opts))
|
.action((branch: string, opts: { force?: boolean }) => closeAction(branch, opts))
|
||||||
|
|
||||||
// ── Branch ──────────────────────────────────────────────────────────
|
// ── sandlot merge ────────────────────────────────────────────────────
|
||||||
|
|
||||||
program.commandsGroup("Branch Commands:")
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("diff")
|
.command("merge")
|
||||||
.argument("<branch>", "branch name")
|
.argument("<branch>", "branch name")
|
||||||
.description("Show uncommitted changes, or full branch diff vs main")
|
.description("Merge a branch into main and close the session")
|
||||||
.action(diffAction)
|
.action(mergeAction)
|
||||||
|
|
||||||
|
// ── sandlot squash ───────────────────────────────────────────────────
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("log")
|
.command("squash")
|
||||||
.argument("<branch>", "branch name")
|
.argument("<branch>", "branch name")
|
||||||
.description("Show commits on a branch that are not on main")
|
.description("Squash-merge a branch into main and close the session")
|
||||||
.action(logAction)
|
.action(squashAction)
|
||||||
|
|
||||||
|
// ── sandlot rebase ───────────────────────────────────────────────────
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("show")
|
.command("rebase")
|
||||||
.argument("<branch>", "branch name")
|
.argument("<branch>", "branch name")
|
||||||
.description("Show the prompt and full diff for a branch")
|
.description("Rebase a branch onto the latest main")
|
||||||
.action(showAction)
|
.action(rebaseAction)
|
||||||
|
|
||||||
|
// ── sandlot save ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("save")
|
.command("save")
|
||||||
|
|
@ -108,36 +123,31 @@ program
|
||||||
.description("Stage all changes and commit")
|
.description("Stage all changes and commit")
|
||||||
.action(saveAction)
|
.action(saveAction)
|
||||||
|
|
||||||
program
|
// ── sandlot diff ─────────────────────────────────────────────────────
|
||||||
.command("merge")
|
|
||||||
.argument("<branch>", "branch name")
|
|
||||||
.description("Merge a branch into main and close the session")
|
|
||||||
.action(mergeAction)
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("squash")
|
.command("diff")
|
||||||
.argument("<branch>", "branch name")
|
.argument("<branch>", "branch name")
|
||||||
.description("Squash-merge a branch into main and close the session")
|
.description("Show uncommitted changes, or full branch diff vs main")
|
||||||
.action(squashAction)
|
.action(diffAction)
|
||||||
|
|
||||||
|
// ── sandlot show ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("rebase")
|
.command("show")
|
||||||
.argument("<branch>", "branch name")
|
.argument("<branch>", "branch name")
|
||||||
.description("Rebase a branch onto the latest main")
|
.description("Show the prompt and full diff for a branch (for code review)")
|
||||||
.action(rebaseAction)
|
.action(showAction)
|
||||||
|
|
||||||
|
// ── sandlot log ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("review")
|
.command("log")
|
||||||
.argument("<branch>", "branch name")
|
.argument("<branch>", "branch name")
|
||||||
.option("-p, --print", "print the review to stdout instead of launching interactive mode")
|
.description("Show commits on a branch that are not on main")
|
||||||
.description("Launch an interactive grumpy code review for a branch")
|
.action(logAction)
|
||||||
.action(reviewAction)
|
|
||||||
|
|
||||||
program
|
// ── sandlot dir ──────────────────────────────────────────────────────
|
||||||
.command("shell")
|
|
||||||
.argument("[branch]", "branch name (omit for a plain VM shell)")
|
|
||||||
.description("Open a shell in the VM")
|
|
||||||
.action(shellAction)
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("dir")
|
.command("dir")
|
||||||
|
|
@ -145,23 +155,18 @@ program
|
||||||
.description("Print the worktree path for a session")
|
.description("Print the worktree path for a session")
|
||||||
.action(dirAction)
|
.action(dirAction)
|
||||||
|
|
||||||
// ── Admin ───────────────────────────────────────────────────────────
|
// ── sandlot cleanup ──────────────────────────────────────────────────
|
||||||
|
|
||||||
program.commandsGroup("Admin Commands:")
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("cleanup")
|
.command("cleanup")
|
||||||
.description("Remove stale sessions whose worktrees no longer exist")
|
.description("Remove stale sessions whose worktrees no longer exist")
|
||||||
.action(cleanupAction)
|
.action(cleanupAction)
|
||||||
|
|
||||||
|
// ── sandlot vm ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
registerVmCommands(program)
|
registerVmCommands(program)
|
||||||
|
|
||||||
program
|
// ── sandlot completions ──────────────────────────────────────────────
|
||||||
.command("version")
|
|
||||||
.description("Print the version number")
|
|
||||||
.action(() => {
|
|
||||||
console.log(pkg.version)
|
|
||||||
})
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("completions")
|
.command("completions")
|
||||||
|
|
@ -169,11 +174,18 @@ program
|
||||||
.description("Output fish shell completions")
|
.description("Output fish shell completions")
|
||||||
.action((opts: { install?: boolean }) => completionsAction(program, opts))
|
.action((opts: { install?: boolean }) => completionsAction(program, opts))
|
||||||
|
|
||||||
// ── Default: `sandlot` → `sandlot list` ─────────────────────────────
|
// ── Default: show list if sessions exist, otherwise help ─────────────
|
||||||
|
|
||||||
if (process.argv.length === 2) {
|
const args = process.argv.slice(2)
|
||||||
|
if (args.length === 0) {
|
||||||
|
try {
|
||||||
|
const root = await git.repoRoot()
|
||||||
|
const st = await state.load(root)
|
||||||
|
if (Object.keys(st.sessions).length > 0) {
|
||||||
process.argv.push("list")
|
process.argv.push("list")
|
||||||
}
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
program.parseAsync().catch((err) => {
|
program.parseAsync().catch((err) => {
|
||||||
console.error(`✖ ${err.message ?? err}`)
|
console.error(`✖ ${err.message ?? err}`)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import * as git from "../git.ts"
|
import * as git from "../git.ts"
|
||||||
|
import { pager } from "../fmt.ts"
|
||||||
import { requireSession } from "./helpers.ts"
|
import { requireSession } from "./helpers.ts"
|
||||||
|
|
||||||
export async function action(branch: string) {
|
export async function action(branch: string) {
|
||||||
|
|
@ -12,26 +13,27 @@ export async function action(branch: string) {
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let args: string[]
|
let diff: string
|
||||||
if (status.text().trim().length > 0) {
|
if (status.text().trim().length > 0) {
|
||||||
// Show uncommitted changes (both staged and unstaged)
|
// Show uncommitted changes (both staged and unstaged)
|
||||||
const hasHead = await $`git -C ${session.worktree} rev-parse --verify HEAD`.nothrow().quiet()
|
const result = await $`git -C ${session.worktree} diff --color=always HEAD`.nothrow().quiet()
|
||||||
args = hasHead.exitCode === 0 ? ["diff", "HEAD"] : ["diff"]
|
if (result.exitCode !== 0) {
|
||||||
|
// HEAD may not exist yet (no commits); fall back to showing all tracked + untracked
|
||||||
|
const fallback = await $`git -C ${session.worktree} diff --color=always`.nothrow().quiet()
|
||||||
|
diff = fallback.text()
|
||||||
|
} else {
|
||||||
|
diff = result.text()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// No uncommitted changes — show full branch diff vs main
|
// No uncommitted changes — show full branch diff vs main
|
||||||
const main = await git.mainBranch(session.worktree)
|
const main = await git.mainBranch(session.worktree)
|
||||||
args = ["diff", `${main}...${branch}`]
|
const result = await $`git -C ${session.worktree} diff --color=always ${main}...${branch}`.nothrow().quiet()
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
console.error("✖ git diff failed")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
diff = result.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run git diff with inherited stdio so external diff tools (e.g. difftastic)
|
await pager(diff)
|
||||||
// see a real TTY and git can use its own pager
|
|
||||||
const proc = Bun.spawn(["git", "-C", session.worktree, ...args], {
|
|
||||||
stdin: "inherit",
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
const exitCode = await proc.exited
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
process.exit(exitCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,10 @@ export async function action(
|
||||||
|
|
||||||
if (opts.print) {
|
if (opts.print) {
|
||||||
spin.text = "Running prompt…"
|
spin.text = "Running prompt…"
|
||||||
const result = await vm.claude(worktreeAbs, { prompt, print: opts.print })
|
const output = await vm.claude(worktreeAbs, { prompt, print: opts.print })
|
||||||
if (result.output) {
|
if (output) {
|
||||||
spin.stop()
|
spin.stop()
|
||||||
process.stdout.write(renderMarkdown(result.output) + "\n")
|
process.stdout.write(renderMarkdown(output) + "\n")
|
||||||
} else {
|
} else {
|
||||||
spin.succeed("Done")
|
spin.succeed("Done")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ export async function action(
|
||||||
|
|
||||||
if (opts.print) {
|
if (opts.print) {
|
||||||
spin.text = "Running prompt…"
|
spin.text = "Running prompt…"
|
||||||
const result = await vm.claude(session.worktree, { prompt, print: opts.print, continue: true })
|
const output = await vm.claude(session.worktree, { prompt, print: opts.print })
|
||||||
if (result.output) {
|
if (output) {
|
||||||
spin.stop()
|
spin.stop()
|
||||||
process.stdout.write(renderMarkdown(result.output) + "\n")
|
process.stdout.write(renderMarkdown(output) + "\n")
|
||||||
} else {
|
} else {
|
||||||
spin.succeed("Done")
|
spin.succeed("Done")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ export async function action(branch: string, opts: { print?: boolean }) {
|
||||||
|
|
||||||
if (opts.print) {
|
if (opts.print) {
|
||||||
spin.text = "Running review…"
|
spin.text = "Running review…"
|
||||||
const result = await vm.claude(session.worktree, { print: prompt })
|
const output = await vm.claude(session.worktree, { print: prompt })
|
||||||
spin.stop()
|
spin.stop()
|
||||||
if (result.output) process.stdout.write(result.output + "\n")
|
if (output) process.stdout.write(output + "\n")
|
||||||
} else {
|
} else {
|
||||||
spin.succeed("Session ready")
|
spin.succeed("Session ready")
|
||||||
await vm.claude(session.worktree, { prompt })
|
await vm.claude(session.worktree, { prompt })
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
|
import { $ } from "bun"
|
||||||
import * as git from "../git.ts"
|
import * as git from "../git.ts"
|
||||||
|
import { pager } from "../fmt.ts"
|
||||||
import { requireSession } from "./helpers.ts"
|
import { requireSession } from "./helpers.ts"
|
||||||
|
|
||||||
export async function action(branch: string) {
|
export async function action(branch: string) {
|
||||||
const { session } = await requireSession(branch)
|
const { session } = await requireSession(branch)
|
||||||
|
|
||||||
if (session.prompt) {
|
|
||||||
process.stderr.write(`PROMPT: ${session.prompt}\n\n`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const main = await git.mainBranch(session.worktree)
|
const main = await git.mainBranch(session.worktree)
|
||||||
|
const result = await $`git -C ${session.worktree} diff --color=always ${main}...${branch}`.nothrow().quiet()
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
console.error("git diff failed")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// Run git diff with inherited stdio so external diff tools (e.g. difftastic)
|
let output = ""
|
||||||
// see a real TTY and git can use its own pager
|
if (session.prompt) {
|
||||||
const proc = Bun.spawn(["git", "-C", session.worktree, "diff", `${main}...${branch}`], {
|
output += `PROMPT: ${session.prompt}\n\n`
|
||||||
stdin: "inherit",
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
const exitCode = await proc.exited
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
process.exit(exitCode)
|
|
||||||
}
|
}
|
||||||
|
output += result.text()
|
||||||
|
|
||||||
|
await pager(output)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export function register(program: Command) {
|
||||||
.description("Show VM system info (via neofetch)")
|
.description("Show VM system info (via neofetch)")
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
await vm.ensure()
|
await vm.ensure()
|
||||||
await vm.neofetch()
|
await vm.info()
|
||||||
})
|
})
|
||||||
|
|
||||||
vmCmd
|
vmCmd
|
||||||
|
|
|
||||||
22
src/vm.ts
22
src/vm.ts
|
|
@ -2,7 +2,6 @@ import { $ } from "bun"
|
||||||
import { homedir } from "os"
|
import { homedir } from "os"
|
||||||
import { dirname, join } from "path"
|
import { dirname, join } from "path"
|
||||||
import { getApiKey } from "./env.ts"
|
import { getApiKey } from "./env.ts"
|
||||||
import { info } from "./fmt.ts"
|
|
||||||
|
|
||||||
const CONTAINER_NAME = "sandlot"
|
const CONTAINER_NAME = "sandlot"
|
||||||
const USER = "ubuntu"
|
const USER = "ubuntu"
|
||||||
|
|
@ -202,7 +201,7 @@ export async function start(): Promise<void> {
|
||||||
const s = await status()
|
const s = await status()
|
||||||
if (s === "running") return
|
if (s === "running") return
|
||||||
if (s === "missing") throw new Error("Container does not exist. Use 'sandlot vm create' first.")
|
if (s === "missing") throw new Error("Container does not exist. Use 'sandlot vm create' first.")
|
||||||
await run($`container start ${CONTAINER_NAME}`, "Container start")
|
await $`container start ${CONTAINER_NAME}`.quiet()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ensure the sandlot container exists and is running. Creates and provisions on first use. */
|
/** Ensure the sandlot container exists and is running. Creates and provisions on first use. */
|
||||||
|
|
@ -238,7 +237,7 @@ export async function status(): Promise<"running" | "stopped" | "missing"> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Launch claude in the container at the given workdir. */
|
/** Launch claude in the container at the given workdir. */
|
||||||
export async function claude(workdir: string, opts?: { prompt?: string; print?: string; continue?: boolean }): Promise<{ exitCode: number; output?: string }> {
|
export async function claude(workdir: string, opts?: { prompt?: string; print?: string; continue?: boolean }): Promise<string | void> {
|
||||||
const cwd = containerPath(workdir)
|
const cwd = containerPath(workdir)
|
||||||
const systemPromptLines = [
|
const systemPromptLines = [
|
||||||
"You are running inside a sandlot container (Apple Container, ubuntu:24.04).",
|
"You are running inside a sandlot container (Apple Container, ubuntu:24.04).",
|
||||||
|
|
@ -261,21 +260,12 @@ export async function claude(workdir: string, opts?: { prompt?: string; print?:
|
||||||
if (opts?.print) {
|
if (opts?.print) {
|
||||||
const proc = Bun.spawn(args, { stdin: "inherit", stdout: "pipe", stderr: "inherit" })
|
const proc = Bun.spawn(args, { stdin: "inherit", stdout: "pipe", stderr: "inherit" })
|
||||||
const output = await new Response(proc.stdout).text()
|
const output = await new Response(proc.stdout).text()
|
||||||
const exitCode = await proc.exited
|
await proc.exited
|
||||||
if (exitCode !== 0 && opts?.continue) {
|
return output
|
||||||
info("Retrying without --continue")
|
|
||||||
return claude(workdir, { ...opts, continue: false })
|
|
||||||
}
|
|
||||||
return { exitCode, output }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const proc = Bun.spawn(args, { stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
const proc = Bun.spawn(args, { stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
||||||
const exitCode = await proc.exited
|
await proc.exited
|
||||||
if (exitCode !== 0 && opts?.continue) {
|
|
||||||
info("Retrying without --continue")
|
|
||||||
return claude(workdir, { ...opts, continue: false })
|
|
||||||
}
|
|
||||||
return { exitCode }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Open an interactive fish shell in the container, optionally in a specific directory. */
|
/** Open an interactive fish shell in the container, optionally in a specific directory. */
|
||||||
|
|
@ -288,7 +278,7 @@ export async function shell(workdir?: string): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Run neofetch in the container. */
|
/** Run neofetch in the container. */
|
||||||
export async function neofetch(): Promise<void> {
|
export async function info(): Promise<void> {
|
||||||
const proc = Bun.spawn(
|
const proc = Bun.spawn(
|
||||||
["container", "exec", "--user", USER, CONTAINER_NAME, "env", `PATH=${CONTAINER_PATH}`, "neofetch"],
|
["container", "exec", "--user", USER, CONTAINER_NAME, "env", `PATH=${CONTAINER_PATH}`, "neofetch"],
|
||||||
{ stdin: "inherit", stdout: "inherit", stderr: "inherit" },
|
{ stdin: "inherit", stdout: "inherit", stderr: "inherit" },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user