From 1d55cf427e533f96401d7726031e6f47985cce69 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:52:22 -0800 Subject: [PATCH] no oauth --- src/commands/new.ts | 5 ++--- src/env.ts | 8 ++++++++ src/vm.ts | 29 +++++++++++------------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/commands/new.ts b/src/commands/new.ts index b297d1b..e0995fd 100644 --- a/src/commands/new.ts +++ b/src/commands/new.ts @@ -6,7 +6,7 @@ import * as vm from "../vm.ts" import * as state from "../state.ts" import { spinner } from "../spinner.ts" import { die } from "../fmt.ts" -import { getApiKey } from "../env.ts" +import { requireApiKey } from "../env.ts" import { renderMarkdown } from "../markdown.ts" import { saveChanges } from "./helpers.ts" @@ -21,8 +21,7 @@ function fallbackBranchName(text: string): string { } async function branchFromPrompt(text: string): Promise { - const apiKey = await getApiKey() - if (!apiKey) return fallbackBranchName(text) + const apiKey = await requireApiKey() try { const res = await fetch("https://api.anthropic.com/v1/messages", { diff --git a/src/env.ts b/src/env.ts index 39b1367..7d67047 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,5 +1,6 @@ import { homedir } from "os" import { join } from "path" +import { die } from "./fmt.ts" /** Read the ANTHROPIC_API_KEY from ~/.env. Returns undefined if not found. */ export async function getApiKey(): Promise { @@ -9,3 +10,10 @@ export async function getApiKey(): Promise { const envContent = await envFile.text() return envContent.match(/^(?:export\s+)?ANTHROPIC_API_KEY=["']?([^"'\s]+)["']?/m)?.[1] } + +/** Read the ANTHROPIC_API_KEY from ~/.env, dying if not found. */ +export async function requireApiKey(): Promise { + const key = await getApiKey() + if (!key) die("ANTHROPIC_API_KEY not found in ~/.env") + return key +} diff --git a/src/vm.ts b/src/vm.ts index d3dc568..36eaf69 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -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 { +async function configureEnvironment(home: string, apiKey: string): Promise { 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 { 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 { 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 { /** Ensure the sandlot container exists and is running. Creates and provisions on first use. */ export async function ensure(log?: (msg: string) => void): Promise { 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()