Compare commits

...

9 Commits

Author SHA1 Message Date
Dotta
59507f18ec Allow onboarding to continue after failed env test 2026-03-04 14:46:29 -06:00
Dotta
b198b4a02c fix(server): require embedded-postgres for embedded DB mode 2026-03-04 14:46:03 -06:00
Dotta
5606f76ab4 Add onboarding retry action to unset ANTHROPIC_API_KEY 2026-03-04 14:40:12 -06:00
Dotta
0542f555ba Fix markdown list markers in editor and comment rendering 2026-03-04 12:20:29 -06:00
Dotta
18c9eb7b1e Filter out archived companies from new issue company selector
Companies with status "archived" are now hidden from the company
dropdown in the NewIssueDialog, matching the expected behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:20:14 -06:00
Dotta
675e0dcff1 docs: document issue search (q= param) in Paperclip skill
The API already supports full-text search via ?q= on the issues list
endpoint. Added documentation to both SKILL.md and the API reference
so agents know they can search issues by title, identifier,
description, and comments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:35:13 -06:00
Dotta
b66c6d017a Adjust docker onboard smoke defaults and console guidance 2026-03-04 10:48:36 -06:00
Dotta
bbf7490f32 Fix onboard smoke Docker flow for clean npx runs 2026-03-04 10:42:07 -06:00
Dotta
5dffdbb382 chore: release v0.2.6 2026-03-04 10:24:03 -06:00
28 changed files with 239 additions and 54 deletions

View File

@@ -2,17 +2,22 @@ FROM ubuntu:24.04
ARG NODE_MAJOR=20
ARG PAPERCLIPAI_VERSION=latest
ARG HOST_UID=10001
ENV DEBIAN_FRONTEND=noninteractive \
PAPERCLIP_HOME=/paperclip \
PAPERCLIP_OPEN_ON_LISTEN=false \
HOST=0.0.0.0 \
PORT=3100 \
HOME=/home/paperclip \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \
NPM_CONFIG_UPDATE_NOTIFIER=false \
NODE_MAJOR=${NODE_MAJOR} \
PAPERCLIPAI_VERSION=${PAPERCLIPAI_VERSION}
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg \
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg locales \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
@@ -20,10 +25,16 @@ RUN apt-get update \
> /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& locale-gen en_US.UTF-8 \
&& groupadd --gid 10001 paperclip \
&& useradd --create-home --shell /bin/bash --uid "${HOST_UID}" --gid 10001 paperclip \
&& mkdir -p /paperclip /home/paperclip/workspace \
&& chown -R paperclip:paperclip /paperclip /home/paperclip \
&& rm -rf /var/lib/apt/lists/*
VOLUME ["/paperclip"]
WORKDIR /workspace
WORKDIR /home/paperclip/workspace
EXPOSE 3100
USER paperclip
CMD ["bash", "-lc", "set -euo pipefail; mkdir -p \"$PAPERCLIP_HOME\"; npx --yes \"paperclipai@${PAPERCLIPAI_VERSION}\" onboard --yes --data-dir \"$PAPERCLIP_HOME\""]

View File

@@ -1,5 +1,19 @@
# paperclipai
## 0.2.6
### Patch Changes
- Version bump (patch)
- Updated dependencies
- @paperclipai/shared@0.2.6
- @paperclipai/adapter-utils@0.2.6
- @paperclipai/db@0.2.6
- @paperclipai/adapter-claude-local@0.2.6
- @paperclipai/adapter-codex-local@0.2.6
- @paperclipai/adapter-openclaw@0.2.6
- @paperclipai/server@0.2.6
## 0.2.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "paperclipai",
"version": "0.2.5",
"version": "0.2.6",
"description": "Paperclip CLI — orchestrate AI agent teams to run a business",
"type": "module",
"bin": {

View File

@@ -23,7 +23,7 @@ const DATA_DIR_OPTION_HELP =
program
.name("paperclipai")
.description("Paperclip CLI — setup, diagnose, and configure your instance")
.version("0.2.5");
.version("0.2.6");
program.hook("preAction", (_thisCommand, actionCommand) => {
const options = actionCommand.optsWithGlobals() as DataDirOptionLike;

View File

@@ -73,6 +73,7 @@ Use this when you want to mimic a fresh machine that only has Ubuntu + npm and v
- `npx paperclipai onboard --yes` completes
- the server binds to `0.0.0.0:3100` so host access works
- onboard/run banners and startup logs are visible in your terminal
Build + run:
@@ -80,15 +81,20 @@ Build + run:
./scripts/docker-onboard-smoke.sh
```
Open: `http://localhost:3100`
Open: `http://localhost:3131` (default smoke host port)
Useful overrides:
```sh
HOST_PORT=3200 PAPERCLIPAI_VERSION=latest ./scripts/docker-onboard-smoke.sh
PAPERCLIP_DEPLOYMENT_MODE=authenticated PAPERCLIP_DEPLOYMENT_EXPOSURE=private ./scripts/docker-onboard-smoke.sh
```
Notes:
- Persistent data is mounted at `./data/docker-onboard-smoke` by default.
- Container runtime user id defaults to your local `id -u` so the mounted data dir stays writable while avoiding root runtime.
- Smoke script defaults to `authenticated/private` mode so `HOST=0.0.0.0` can be exposed to the host.
- Smoke script defaults host port to `3131` to avoid conflicts with local Paperclip on `3100`.
- Run the script in the foreground to watch the onboarding flow; stop with `Ctrl+C` after validation.
- The image definition is in `Dockerfile.onboard-smoke`.

View File

@@ -1,5 +1,11 @@
# @paperclipai/adapter-utils
## 0.2.6
### Patch Changes
- Version bump (patch)
## 0.2.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@paperclipai/adapter-utils",
"version": "0.2.5",
"version": "0.2.6",
"type": "module",
"exports": {
".": "./src/index.ts",

View File

@@ -1,5 +1,13 @@
# @paperclipai/adapter-claude-local
## 0.2.6
### Patch Changes
- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.6
## 0.2.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@paperclipai/adapter-claude-local",
"version": "0.2.5",
"version": "0.2.6",
"type": "module",
"exports": {
".": "./src/index.ts",

View File

@@ -1,5 +1,13 @@
# @paperclipai/adapter-codex-local
## 0.2.6
### Patch Changes
- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.6
## 0.2.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@paperclipai/adapter-codex-local",
"version": "0.2.5",
"version": "0.2.6",
"type": "module",
"exports": {
".": "./src/index.ts",

View File

@@ -1,5 +1,13 @@
# @paperclipai/adapter-openclaw
## 0.2.6
### Patch Changes
- Version bump (patch)
- Updated dependencies
- @paperclipai/adapter-utils@0.2.6
## 0.2.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@paperclipai/adapter-openclaw",
"version": "0.2.5",
"version": "0.2.6",
"type": "module",
"exports": {
".": "./src/index.ts",

View File

@@ -1,5 +1,13 @@
# @paperclipai/db
## 0.2.6
### Patch Changes
- Version bump (patch)
- Updated dependencies
- @paperclipai/shared@0.2.6
## 0.2.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@paperclipai/db",
"version": "0.2.5",
"version": "0.2.6",
"type": "module",
"exports": {
".": "./src/index.ts",

View File

@@ -1,5 +1,11 @@
# @paperclipai/shared
## 0.2.6
### Patch Changes
- Version bump (patch)
## 0.2.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@paperclipai/shared",
"version": "0.2.5",
"version": "0.2.6",
"type": "module",
"exports": {
".": "./src/index.ts",

34
pnpm-lock.yaml generated
View File

@@ -185,6 +185,9 @@ importers:
drizzle-orm:
specifier: ^0.38.4
version: 0.38.4(@electric-sql/pglite@0.3.15)(@types/react@19.2.14)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8)(react@19.2.4)
embedded-postgres:
specifier: ^18.1.0-beta.16
version: 18.1.0-beta.16
express:
specifier: ^5.1.0
version: 5.2.1
@@ -209,10 +212,6 @@ importers:
zod:
specifier: ^3.24.2
version: 3.25.76
optionalDependencies:
embedded-postgres:
specifier: ^18.1.0-beta.16
version: 18.1.0-beta.16
devDependencies:
'@types/express':
specifier: ^5.0.0
@@ -8285,8 +8284,7 @@ snapshots:
assertion-error@2.0.1: {}
async-exit-hook@2.0.1:
optional: true
async-exit-hook@2.0.1: {}
asynckit@0.4.0: {}
@@ -8615,7 +8613,6 @@ snapshots:
'@embedded-postgres/windows-x64': 18.1.0-beta.16
transitivePeerDependencies:
- pg-native
optional: true
encodeurl@2.0.0: {}
@@ -9808,19 +9805,15 @@ snapshots:
pg-cloudflare@1.3.0:
optional: true
pg-connection-string@2.11.0:
optional: true
pg-connection-string@2.11.0: {}
pg-int8@1.0.1:
optional: true
pg-int8@1.0.1: {}
pg-pool@3.11.0(pg@8.18.0):
dependencies:
pg: 8.18.0
optional: true
pg-protocol@1.11.0:
optional: true
pg-protocol@1.11.0: {}
pg-types@2.2.0:
dependencies:
@@ -9829,7 +9822,6 @@ snapshots:
postgres-bytea: 1.0.1
postgres-date: 1.0.7
postgres-interval: 1.2.0
optional: true
pg@8.18.0:
dependencies:
@@ -9840,12 +9832,10 @@ snapshots:
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.3.0
optional: true
pgpass@1.0.5:
dependencies:
split2: 4.2.0
optional: true
picocolors@1.1.1: {}
@@ -9913,19 +9903,15 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
postgres-array@2.0.0:
optional: true
postgres-array@2.0.0: {}
postgres-bytea@1.0.1:
optional: true
postgres-bytea@1.0.1: {}
postgres-date@1.0.7:
optional: true
postgres-date@1.0.7: {}
postgres-interval@1.2.0:
dependencies:
xtend: 4.0.2
optional: true
postgres@3.4.8: {}

View File

@@ -3,15 +3,24 @@ set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE_NAME="${IMAGE_NAME:-paperclip-onboard-smoke}"
HOST_PORT="${HOST_PORT:-3100}"
HOST_PORT="${HOST_PORT:-3131}"
PAPERCLIPAI_VERSION="${PAPERCLIPAI_VERSION:-latest}"
DATA_DIR="${DATA_DIR:-$REPO_ROOT/data/docker-onboard-smoke}"
HOST_UID="${HOST_UID:-$(id -u)}"
PAPERCLIP_DEPLOYMENT_MODE="${PAPERCLIP_DEPLOYMENT_MODE:-authenticated}"
PAPERCLIP_DEPLOYMENT_EXPOSURE="${PAPERCLIP_DEPLOYMENT_EXPOSURE:-private}"
DOCKER_TTY_ARGS=()
if [[ -t 0 && -t 1 ]]; then
DOCKER_TTY_ARGS=(-it)
fi
mkdir -p "$DATA_DIR"
echo "==> Building onboard smoke image"
docker build \
--build-arg PAPERCLIPAI_VERSION="$PAPERCLIPAI_VERSION" \
--build-arg HOST_UID="$HOST_UID" \
-f "$REPO_ROOT/Dockerfile.onboard-smoke" \
-t "$IMAGE_NAME" \
"$REPO_ROOT"
@@ -19,10 +28,15 @@ docker build \
echo "==> Running onboard smoke container"
echo " UI should be reachable at: http://localhost:$HOST_PORT"
echo " Data dir: $DATA_DIR"
echo " Deployment: $PAPERCLIP_DEPLOYMENT_MODE/$PAPERCLIP_DEPLOYMENT_EXPOSURE"
echo " Live output: onboard banner and server logs stream in this terminal (Ctrl+C to stop)"
docker run --rm \
"${DOCKER_TTY_ARGS[@]}" \
--name "${IMAGE_NAME//[^a-zA-Z0-9_.-]/-}" \
-p "$HOST_PORT:3100" \
-e HOST=0.0.0.0 \
-e PORT=3100 \
-e PAPERCLIP_DEPLOYMENT_MODE="$PAPERCLIP_DEPLOYMENT_MODE" \
-e PAPERCLIP_DEPLOYMENT_EXPOSURE="$PAPERCLIP_DEPLOYMENT_EXPOSURE" \
-v "$DATA_DIR:/paperclip" \
"$IMAGE_NAME"

View File

@@ -1,5 +1,18 @@
# @paperclipai/server
## 0.2.6
### Patch Changes
- Version bump (patch)
- Updated dependencies
- @paperclipai/shared@0.2.6
- @paperclipai/adapter-utils@0.2.6
- @paperclipai/db@0.2.6
- @paperclipai/adapter-claude-local@0.2.6
- @paperclipai/adapter-codex-local@0.2.6
- @paperclipai/adapter-openclaw@0.2.6
## 0.2.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@paperclipai/server",
"version": "0.2.5",
"version": "0.2.6",
"type": "module",
"exports": {
".": "./src/index.ts"
@@ -41,6 +41,7 @@
"detect-port": "^2.1.0",
"dotenv": "^17.0.1",
"drizzle-orm": "^0.38.4",
"embedded-postgres": "^18.1.0-beta.16",
"express": "^5.1.0",
"multer": "^2.0.2",
"open": "^11.0.0",
@@ -50,9 +51,6 @@
"ws": "^8.19.0",
"zod": "^3.24.2"
},
"optionalDependencies": {
"embedded-postgres": "^18.1.0-beta.16"
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/express-serve-static-core": "^5.0.0",

View File

@@ -237,7 +237,7 @@ if (config.databaseUrl) {
EmbeddedPostgres = mod.default as EmbeddedPostgresCtor;
} catch {
throw new Error(
"Embedded PostgreSQL mode requires optional dependency `embedded-postgres`. Install optional dependencies or set DATABASE_URL for external Postgres.",
"Embedded PostgreSQL mode requires dependency `embedded-postgres`. Reinstall dependencies (without omitting required packages), or set DATABASE_URL for external Postgres.",
);
}

View File

@@ -207,6 +207,17 @@ PATCH /api/agents/{agentId}/instructions-path
| Release task | `POST /api/issues/:issueId/release` |
| List agents | `GET /api/companies/:companyId/agents` |
| Dashboard | `GET /api/companies/:companyId/dashboard` |
| Search issues | `GET /api/companies/:companyId/issues?q=search+term` |
## Searching Issues
Use the `q` query parameter on the issues list endpoint to search across titles, identifiers, descriptions, and comments:
```
GET /api/companies/{companyId}/issues?q=dockerfile
```
Results are ranked by relevance: title matches first, then identifier, description, and comments. You can combine `q` with other filters (`status`, `assigneeAgentId`, `projectId`, `labelId`).
## Full Reference

View File

@@ -472,7 +472,7 @@ Terminal states: `done`, `cancelled`
| Method | Path | Description |
| ------ | ---------------------------------- | ---------------------------------------------------------------------------------------- |
| GET | `/api/companies/:companyId/issues` | List issues, sorted by priority. Filters: `?status=`, `?assigneeAgentId=`, `?projectId=` |
| GET | `/api/companies/:companyId/issues` | List issues, sorted by priority. Filters: `?status=`, `?assigneeAgentId=`, `?assigneeUserId=`, `?projectId=`, `?labelId=`, `?q=` (full-text search across title, identifier, description, comments) |
| GET | `/api/issues/:issueId` | Issue details + ancestors |
| POST | `/api/companies/:companyId/issues` | Create issue |
| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field adds a comment in same call) |

View File

@@ -38,7 +38,7 @@ export function MarkdownBody({ children, className }: MarkdownBodyProps) {
return (
<div
className={cn(
"prose prose-sm max-w-none prose-p:my-2 prose-p:leading-[1.4] prose-ul:my-1.5 prose-ol:my-1.5 prose-li:my-0.5 prose-li:leading-[1.4] prose-pre:my-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-headings:my-2 prose-headings:text-sm prose-blockquote:leading-[1.4] prose-table:my-2 prose-th:px-3 prose-th:py-1.5 prose-td:px-3 prose-td:py-1.5 prose-code:break-all",
"prose prose-sm max-w-none prose-p:my-2 prose-p:leading-[1.4] prose-ul:my-1.5 prose-ol:my-1.5 prose-li:my-0.5 prose-li:leading-[1.4] prose-pre:my-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-headings:my-2 prose-headings:text-sm prose-blockquote:leading-[1.4] prose-table:my-2 prose-th:px-3 prose-th:py-1.5 prose-td:px-3 prose-td:py-1.5 prose-code:break-all [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_li]:list-item",
theme === "dark" && "prose-invert",
className,
)}

View File

@@ -563,7 +563,7 @@ export const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>
onBlur={() => onBlur?.()}
className={cn("paperclip-mdxeditor", !bordered && "paperclip-mdxeditor--borderless")}
contentEditableClassName={cn(
"paperclip-mdxeditor-content focus:outline-none",
"paperclip-mdxeditor-content focus:outline-none [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_li]:list-item",
contentClassName,
)}
overlayContainer={containerRef.current}

View File

@@ -527,7 +527,7 @@ export function NewIssueDialog() {
</button>
</PopoverTrigger>
<PopoverContent className="w-48 p-1" align="start">
{companies.map((c) => (
{companies.filter((c) => c.status !== "archived").map((c) => (
<button
key={c.id}
className={cn(

View File

@@ -89,6 +89,9 @@ export function OnboardingWizard() {
useState<AdapterEnvironmentTestResult | null>(null);
const [adapterEnvError, setAdapterEnvError] = useState<string | null>(null);
const [adapterEnvLoading, setAdapterEnvLoading] = useState(false);
const [forceUnsetAnthropicApiKey, setForceUnsetAnthropicApiKey] =
useState(false);
const [unsetAnthropicLoading, setUnsetAnthropicLoading] = useState(false);
// Step 3
const [taskTitle, setTaskTitle] = useState("Create your CEO HEARTBEAT.md");
@@ -159,6 +162,15 @@ export function OnboardingWizard() {
}, [step, adapterType, cwd, model, command, args, url]);
const selectedModel = (adapterModels ?? []).find((m) => m.id === model);
const hasAnthropicApiKeyOverrideCheck =
adapterEnvResult?.checks.some(
(check) =>
check.code === "claude_anthropic_api_key_overrides_subscription"
) ?? false;
const shouldSuggestUnsetAnthropicApiKey =
adapterType === "claude_local" &&
adapterEnvResult?.status === "fail" &&
hasAnthropicApiKeyOverrideCheck;
function reset() {
setStep(1);
@@ -176,6 +188,8 @@ export function OnboardingWizard() {
setAdapterEnvResult(null);
setAdapterEnvError(null);
setAdapterEnvLoading(false);
setForceUnsetAnthropicApiKey(false);
setUnsetAnthropicLoading(false);
setTaskTitle("Create your CEO HEARTBEAT.md");
setTaskDescription(DEFAULT_TASK_DESCRIPTION);
setCreatedCompanyId(null);
@@ -191,7 +205,7 @@ export function OnboardingWizard() {
function buildAdapterConfig(): Record<string, unknown> {
const adapter = getUIAdapter(adapterType);
return adapter.buildAdapterConfig({
const config = adapter.buildAdapterConfig({
...defaultCreateValues,
adapterType,
cwd,
@@ -208,9 +222,22 @@ export function OnboardingWizard() {
? DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX
: defaultCreateValues.dangerouslyBypassSandbox
});
if (adapterType === "claude_local" && forceUnsetAnthropicApiKey) {
const env =
typeof config.env === "object" &&
config.env !== null &&
!Array.isArray(config.env)
? { ...(config.env as Record<string, unknown>) }
: {};
env.ANTHROPIC_API_KEY = { type: "plain", value: "" };
config.env = env;
}
return config;
}
async function runAdapterEnvironmentTest(): Promise<AdapterEnvironmentTestResult | null> {
async function runAdapterEnvironmentTest(
adapterConfigOverride?: Record<string, unknown>
): Promise<AdapterEnvironmentTestResult | null> {
if (!createdCompanyId) {
setAdapterEnvError(
"Create or select a company before testing adapter environment."
@@ -224,7 +251,7 @@ export function OnboardingWizard() {
createdCompanyId,
adapterType,
{
adapterConfig: buildAdapterConfig()
adapterConfig: adapterConfigOverride ?? buildAdapterConfig()
}
);
setAdapterEnvResult(result);
@@ -276,12 +303,6 @@ export function OnboardingWizard() {
if (isLocalAdapter) {
const result = adapterEnvResult ?? (await runAdapterEnvironmentTest());
if (!result) return;
if (result.status === "fail") {
setError(
"Adapter environment test failed. Fix the errors and test again before continuing."
);
return;
}
}
const agent = await agentsApi.create(createdCompanyId, {
@@ -311,6 +332,55 @@ export function OnboardingWizard() {
}
}
async function handleUnsetAnthropicApiKey() {
if (!createdCompanyId || unsetAnthropicLoading) return;
setUnsetAnthropicLoading(true);
setError(null);
setAdapterEnvError(null);
setForceUnsetAnthropicApiKey(true);
const configWithUnset = (() => {
const config = buildAdapterConfig();
const env =
typeof config.env === "object" &&
config.env !== null &&
!Array.isArray(config.env)
? { ...(config.env as Record<string, unknown>) }
: {};
env.ANTHROPIC_API_KEY = { type: "plain", value: "" };
config.env = env;
return config;
})();
try {
if (createdAgentId) {
await agentsApi.update(
createdAgentId,
{ adapterConfig: configWithUnset },
createdCompanyId
);
queryClient.invalidateQueries({
queryKey: queryKeys.agents.list(createdCompanyId)
});
}
const result = await runAdapterEnvironmentTest(configWithUnset);
if (result?.status === "fail") {
setError(
"Retried with ANTHROPIC_API_KEY unset in adapter config, but the environment test is still failing."
);
}
} catch (err) {
setError(
err instanceof Error
? err.message
: "Failed to unset ANTHROPIC_API_KEY and retry."
);
} finally {
setUnsetAnthropicLoading(false);
}
}
async function handleStep3Next() {
if (!createdCompanyId || !createdAgentId) return;
setLoading(true);
@@ -673,6 +743,24 @@ export function OnboardingWizard() {
<AdapterEnvironmentResult result={adapterEnvResult} />
)}
{shouldSuggestUnsetAnthropicApiKey && (
<div className="rounded-md border border-amber-300/60 bg-amber-50/40 px-2.5 py-2 space-y-2">
<p className="text-[11px] text-amber-900/90 leading-relaxed">
Claude failed while <span className="font-mono">ANTHROPIC_API_KEY</span> is set.
You can clear it in this CEO adapter config and retry the probe.
</p>
<Button
size="sm"
variant="outline"
className="h-7 px-2.5 text-xs"
disabled={adapterEnvLoading || unsetAnthropicLoading}
onClick={() => void handleUnsetAnthropicApiKey()}
>
{unsetAnthropicLoading ? "Retrying..." : "Unset ANTHROPIC_API_KEY"}
</Button>
</div>
)}
<div className="rounded-md border border-border/70 bg-muted/20 px-2.5 py-2 text-[11px] space-y-1.5">
<p className="font-medium">Manual debug</p>
<p className="text-muted-foreground font-mono break-all">