Compare commits
9 Commits
@paperclip
...
@paperclip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59507f18ec | ||
|
|
b198b4a02c | ||
|
|
5606f76ab4 | ||
|
|
0542f555ba | ||
|
|
18c9eb7b1e | ||
|
|
675e0dcff1 | ||
|
|
b66c6d017a | ||
|
|
bbf7490f32 | ||
|
|
5dffdbb382 |
@@ -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\""]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @paperclipai/adapter-utils
|
||||
|
||||
## 0.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Version bump (patch)
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@paperclipai/adapter-utils",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@paperclipai/adapter-claude-local",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@paperclipai/adapter-codex-local",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@paperclipai/adapter-openclaw",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@paperclipai/db",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @paperclipai/shared
|
||||
|
||||
## 0.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Version bump (patch)
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -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
34
pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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,
|
||||
)}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user