|
|
|
|
@ -2,7 +2,7 @@ import { $ } from "bun"
|
|
|
|
|
import { existsSync } from "fs"
|
|
|
|
|
import { homedir } from "os"
|
|
|
|
|
import { dirname, join } from "path"
|
|
|
|
|
import { getApiKey } from "./env.ts"
|
|
|
|
|
import { requireApiKey } from "./env.ts"
|
|
|
|
|
import { info } from "./fmt.ts"
|
|
|
|
|
|
|
|
|
|
const DEBUG = !!process.env.DEBUG
|
|
|
|
|
@ -145,35 +145,27 @@ async function installTooling(cached: boolean, log?: (msg: string) => void): Pro
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Configure git identity, API key helper, activity hook, and Claude settings. */
|
|
|
|
|
async function configureEnvironment(home: string, log?: (msg: string) => void, apiKey?: string): Promise<void> {
|
|
|
|
|
async function configureEnvironment(home: string, apiKey: string): Promise<void> {
|
|
|
|
|
const gitName = (await $`git config user.name`.quiet().text()).trim()
|
|
|
|
|
const gitEmail = (await $`git config user.email`.quiet().text()).trim()
|
|
|
|
|
if (gitName) await $`container exec --user ${USER} ${CONTAINER_NAME} git config --global user.name ${gitName}`.quiet()
|
|
|
|
|
if (gitEmail) await $`container exec --user ${USER} ${CONTAINER_NAME} git config --global user.email ${gitEmail}`.quiet()
|
|
|
|
|
|
|
|
|
|
if (!apiKey) {
|
|
|
|
|
log?.("Warning: ANTHROPIC_API_KEY not found in ~/.env — claude will require manual login")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const activityBin = `/home/${USER}/.local/bin/sandlot-activity`
|
|
|
|
|
const hooks = {
|
|
|
|
|
UserPromptSubmit: [{ hooks: [{ type: "command", command: `${activityBin} active` }] }],
|
|
|
|
|
Stop: [{ hooks: [{ type: "command", command: `${activityBin} idle` }] }],
|
|
|
|
|
}
|
|
|
|
|
const settingsJson = JSON.stringify(apiKey
|
|
|
|
|
? { apiKeyHelper: "~/.claude/api-key-helper.sh", skipDangerousModePermissionPrompt: true, hooks }
|
|
|
|
|
: { skipDangerousModePermissionPrompt: true, hooks })
|
|
|
|
|
const settingsJson = JSON.stringify({ apiKeyHelper: "~/.claude/api-key-helper.sh", skipDangerousModePermissionPrompt: true, hooks })
|
|
|
|
|
const claudeJson = JSON.stringify({ hasCompletedOnboarding: true, effortCalloutDismissed: true, projects: { "/": { hasTrustDialogAccepted: true } } })
|
|
|
|
|
|
|
|
|
|
// Write the helper script to a temp file and copy it in so the key
|
|
|
|
|
// never appears in a process argument visible in `ps`.
|
|
|
|
|
if (apiKey) {
|
|
|
|
|
const tmp = `${home}/.sandlot/.api-key-helper.tmp`
|
|
|
|
|
await Bun.write(tmp, `#!/bin/sh\necho '${apiKey.replace(/'/g, "'\\''")}'\n`)
|
|
|
|
|
await $`chmod +x ${tmp}`.quiet()
|
|
|
|
|
await $`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"mkdir -p ~/.claude && cp /sandlot/.api-key-helper.tmp ~/.claude/api-key-helper.sh"}`.quiet()
|
|
|
|
|
await Bun.file(tmp).unlink()
|
|
|
|
|
}
|
|
|
|
|
const tmp = `${home}/.sandlot/.api-key-helper.tmp`
|
|
|
|
|
await Bun.write(tmp, `#!/bin/sh\necho '${apiKey.replace(/'/g, "'\\''")}'\n`)
|
|
|
|
|
await $`chmod +x ${tmp}`.quiet()
|
|
|
|
|
await $`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"mkdir -p ~/.claude && cp /sandlot/.api-key-helper.tmp ~/.claude/api-key-helper.sh"}`.quiet()
|
|
|
|
|
await Bun.file(tmp).unlink()
|
|
|
|
|
|
|
|
|
|
// Install activity tracking hook script
|
|
|
|
|
const activityTmp = `${home}/.sandlot/.sandlot-activity.tmp`
|
|
|
|
|
@ -199,6 +191,7 @@ echo '${claudeJson}' > ~/.claude.json
|
|
|
|
|
/** Create and provision the container from scratch. Fails if it already exists. */
|
|
|
|
|
export async function create(log?: (msg: string) => void): Promise<void> {
|
|
|
|
|
requireContainer()
|
|
|
|
|
const apiKey = await requireApiKey()
|
|
|
|
|
|
|
|
|
|
const s = await status()
|
|
|
|
|
if (s !== "missing") {
|
|
|
|
|
@ -219,8 +212,7 @@ export async function create(log?: (msg: string) => void): Promise<void> {
|
|
|
|
|
await installTooling(cached, log)
|
|
|
|
|
|
|
|
|
|
log?.("Configuring environment")
|
|
|
|
|
const apiKey = await getApiKey()
|
|
|
|
|
await configureEnvironment(home, log, apiKey)
|
|
|
|
|
await configureEnvironment(home, apiKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Start a stopped container. */
|
|
|
|
|
@ -235,6 +227,7 @@ export async function start(): Promise<void> {
|
|
|
|
|
/** Ensure the sandlot container exists and is running. Creates and provisions on first use. */
|
|
|
|
|
export async function ensure(log?: (msg: string) => void): Promise<void> {
|
|
|
|
|
requireContainer()
|
|
|
|
|
await requireApiKey()
|
|
|
|
|
|
|
|
|
|
// Ensure the container daemon is running (--enable-kernel-install skips interactive prompt)
|
|
|
|
|
if (DEBUG) await $`container system start --enable-kernel-install`.nothrow()
|
|
|
|
|
|