Compare commits
19 Commits
@paperclip
...
@paperclip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59507f18ec | ||
|
|
b198b4a02c | ||
|
|
5606f76ab4 | ||
|
|
0542f555ba | ||
|
|
18c9eb7b1e | ||
|
|
675e0dcff1 | ||
|
|
b66c6d017a | ||
|
|
bbf7490f32 | ||
|
|
5dffdbb382 | ||
|
|
ea637110ac | ||
|
|
3ae9d95354 | ||
|
|
a95e38485d | ||
|
|
c7c96feef7 | ||
|
|
7e387a1883 | ||
|
|
108bb9bd15 | ||
|
|
6141d5c3f2 | ||
|
|
a4da932d8d | ||
|
|
ab3b9ab19f | ||
|
|
f4a5b00116 |
40
Dockerfile.onboard-smoke
Normal file
40
Dockerfile.onboard-smoke
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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 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 \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" \
|
||||||
|
> /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 /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\""]
|
||||||
@@ -6,13 +6,13 @@
|
|||||||
<a href="#quickstart"><strong>Quickstart</strong></a> ·
|
<a href="#quickstart"><strong>Quickstart</strong></a> ·
|
||||||
<a href="https://paperclip.ing/docs"><strong>Docs</strong></a> ·
|
<a href="https://paperclip.ing/docs"><strong>Docs</strong></a> ·
|
||||||
<a href="https://github.com/paperclipai/paperclip"><strong>GitHub</strong></a> ·
|
<a href="https://github.com/paperclipai/paperclip"><strong>GitHub</strong></a> ·
|
||||||
<a href="https://discord.gg/paperclip"><strong>Discord</strong></a>
|
<a href="https://discord.gg/m4HZY7xNG3"><strong>Discord</strong></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/paperclipai/paperclip/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License" /></a>
|
<a href="https://github.com/paperclipai/paperclip/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License" /></a>
|
||||||
<a href="https://github.com/paperclipai/paperclip/stargazers"><img src="https://img.shields.io/github/stars/paperclipai/paperclip?style=flat" alt="Stars" /></a>
|
<a href="https://github.com/paperclipai/paperclip/stargazers"><img src="https://img.shields.io/github/stars/paperclipai/paperclip?style=flat" alt="Stars" /></a>
|
||||||
<a href="https://discord.gg/paperclip"><img src="https://img.shields.io/discord/000000000?label=discord" alt="Discord" /></a>
|
<a href="https://discord.gg/m4HZY7xNG3"><img src="https://img.shields.io/discord/000000000?label=discord" alt="Discord" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
@@ -174,7 +174,7 @@ Paperclip handles the hard orchestration details correctly.
|
|||||||
Open source. Self-hosted. No Paperclip account required.
|
Open source. Self-hosted. No Paperclip account required.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx paperclipai onboard
|
npx paperclipai onboard --yes
|
||||||
```
|
```
|
||||||
|
|
||||||
Or manually:
|
Or manually:
|
||||||
@@ -249,7 +249,7 @@ We welcome contributions. See the [contributing guide](CONTRIBUTING.md) for deta
|
|||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
- [Discord](#) — Coming soon
|
- [Discord](https://discord.gg/m4HZY7xNG3) — Join the community
|
||||||
- [GitHub Issues](https://github.com/paperclipai/paperclip/issues) — bugs and feature requests
|
- [GitHub Issues](https://github.com/paperclipai/paperclip/issues) — bugs and feature requests
|
||||||
- [GitHub Discussions](https://github.com/paperclipai/paperclip/discussions) — ideas and RFC
|
- [GitHub Discussions](https://github.com/paperclipai/paperclip/discussions) — ideas and RFC
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,47 @@
|
|||||||
# paperclipai
|
# 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
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/shared@0.2.5
|
||||||
|
- @paperclipai/adapter-utils@0.2.5
|
||||||
|
- @paperclipai/db@0.2.5
|
||||||
|
- @paperclipai/adapter-claude-local@0.2.5
|
||||||
|
- @paperclipai/adapter-codex-local@0.2.5
|
||||||
|
- @paperclipai/adapter-openclaw@0.2.5
|
||||||
|
- @paperclipai/server@0.2.5
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/shared@0.2.4
|
||||||
|
- @paperclipai/adapter-utils@0.2.4
|
||||||
|
- @paperclipai/db@0.2.4
|
||||||
|
- @paperclipai/adapter-claude-local@0.2.4
|
||||||
|
- @paperclipai/adapter-codex-local@0.2.4
|
||||||
|
- @paperclipai/adapter-openclaw@0.2.4
|
||||||
|
- @paperclipai/server@0.2.4
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paperclipai",
|
"name": "paperclipai",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"description": "Paperclip CLI — orchestrate AI agent teams to run a business",
|
"description": "Paperclip CLI — orchestrate AI agent teams to run a business",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -39,15 +39,7 @@ export async function databaseCheck(config: PaperclipConfig, configPath?: string
|
|||||||
const dataDir = resolveRuntimeLikePath(config.database.embeddedPostgresDataDir, configPath);
|
const dataDir = resolveRuntimeLikePath(config.database.embeddedPostgresDataDir, configPath);
|
||||||
const reportedPath = dataDir;
|
const reportedPath = dataDir;
|
||||||
if (!fs.existsSync(dataDir)) {
|
if (!fs.existsSync(dataDir)) {
|
||||||
return {
|
fs.mkdirSync(reportedPath, { recursive: true });
|
||||||
name: "Database",
|
|
||||||
status: "warn",
|
|
||||||
message: `Embedded PostgreSQL data directory does not exist: ${reportedPath}`,
|
|
||||||
canRepair: true,
|
|
||||||
repair: () => {
|
|
||||||
fs.mkdirSync(reportedPath, { recursive: true });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,20 +5,16 @@ export async function llmCheck(config: PaperclipConfig): Promise<CheckResult> {
|
|||||||
if (!config.llm) {
|
if (!config.llm) {
|
||||||
return {
|
return {
|
||||||
name: "LLM provider",
|
name: "LLM provider",
|
||||||
status: "warn",
|
status: "pass",
|
||||||
message: "No LLM provider configured",
|
message: "No LLM provider configured (optional)",
|
||||||
canRepair: false,
|
|
||||||
repairHint: "Run `paperclipai configure --section llm` to set one up",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.llm.apiKey) {
|
if (!config.llm.apiKey) {
|
||||||
return {
|
return {
|
||||||
name: "LLM provider",
|
name: "LLM provider",
|
||||||
status: "warn",
|
status: "pass",
|
||||||
message: `${config.llm.provider} configured but no API key set`,
|
message: `${config.llm.provider} configured but no API key set (optional)`,
|
||||||
canRepair: false,
|
|
||||||
repairHint: "Run `paperclipai configure --section llm`",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,7 @@ export function logCheck(config: PaperclipConfig, configPath?: string): CheckRes
|
|||||||
const reportedDir = logDir;
|
const reportedDir = logDir;
|
||||||
|
|
||||||
if (!fs.existsSync(logDir)) {
|
if (!fs.existsSync(logDir)) {
|
||||||
return {
|
fs.mkdirSync(reportedDir, { recursive: true });
|
||||||
name: "Log directory",
|
|
||||||
status: "warn",
|
|
||||||
message: `Log directory does not exist: ${reportedDir}`,
|
|
||||||
canRepair: true,
|
|
||||||
repair: () => {
|
|
||||||
fs.mkdirSync(reportedDir, { recursive: true });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,16 +7,7 @@ export function storageCheck(config: PaperclipConfig, configPath?: string): Chec
|
|||||||
if (config.storage.provider === "local_disk") {
|
if (config.storage.provider === "local_disk") {
|
||||||
const baseDir = resolveRuntimeLikePath(config.storage.localDisk.baseDir, configPath);
|
const baseDir = resolveRuntimeLikePath(config.storage.localDisk.baseDir, configPath);
|
||||||
if (!fs.existsSync(baseDir)) {
|
if (!fs.existsSync(baseDir)) {
|
||||||
return {
|
fs.mkdirSync(baseDir, { recursive: true });
|
||||||
name: "Storage",
|
|
||||||
status: "warn",
|
|
||||||
message: `Local storage directory does not exist: ${baseDir}`,
|
|
||||||
canRepair: true,
|
|
||||||
repair: () => {
|
|
||||||
fs.mkdirSync(baseDir, { recursive: true });
|
|
||||||
},
|
|
||||||
repairHint: "Run with --repair to create local storage directory",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -84,6 +84,15 @@ function isModuleNotFoundError(err: unknown): boolean {
|
|||||||
return err.message.includes("Cannot find module");
|
return err.message.includes("Cannot find module");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMissingModuleSpecifier(err: unknown): string | null {
|
||||||
|
if (!(err instanceof Error)) return null;
|
||||||
|
const packageMatch = err.message.match(/Cannot find package '([^']+)' imported from/);
|
||||||
|
if (packageMatch?.[1]) return packageMatch[1];
|
||||||
|
const moduleMatch = err.message.match(/Cannot find module '([^']+)'/);
|
||||||
|
if (moduleMatch?.[1]) return moduleMatch[1];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function maybeEnableUiDevMiddleware(entrypoint: string): void {
|
function maybeEnableUiDevMiddleware(entrypoint: string): void {
|
||||||
if (process.env.PAPERCLIP_UI_DEV_MIDDLEWARE !== undefined) return;
|
if (process.env.PAPERCLIP_UI_DEV_MIDDLEWARE !== undefined) return;
|
||||||
const normalized = entrypoint.replaceAll("\\", "/");
|
const normalized = entrypoint.replaceAll("\\", "/");
|
||||||
@@ -106,7 +115,9 @@ async function importServerEntry(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await import("@paperclipai/server");
|
await import("@paperclipai/server");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isModuleNotFoundError(err)) {
|
const missingSpecifier = getMissingModuleSpecifier(err);
|
||||||
|
const missingServerEntrypoint = !missingSpecifier || missingSpecifier === "@paperclipai/server";
|
||||||
|
if (isModuleNotFoundError(err) && missingServerEntrypoint) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not locate a Paperclip server entrypoint.\n` +
|
`Could not locate a Paperclip server entrypoint.\n` +
|
||||||
`Tried: ${devEntry}, @paperclipai/server\n` +
|
`Tried: ${devEntry}, @paperclipai/server\n` +
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const DATA_DIR_OPTION_HELP =
|
|||||||
program
|
program
|
||||||
.name("paperclipai")
|
.name("paperclipai")
|
||||||
.description("Paperclip CLI — setup, diagnose, and configure your instance")
|
.description("Paperclip CLI — setup, diagnose, and configure your instance")
|
||||||
.version("0.2.3");
|
.version("0.2.6");
|
||||||
|
|
||||||
program.hook("preAction", (_thisCommand, actionCommand) => {
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
||||||
const options = actionCommand.optsWithGlobals() as DataDirOptionLike;
|
const options = actionCommand.optsWithGlobals() as DataDirOptionLike;
|
||||||
|
|||||||
@@ -66,3 +66,35 @@ Notes:
|
|||||||
|
|
||||||
- Without API keys, the app still runs normally.
|
- Without API keys, the app still runs normally.
|
||||||
- Adapter environment checks in Paperclip will surface missing auth/CLI prerequisites.
|
- Adapter environment checks in Paperclip will surface missing auth/CLI prerequisites.
|
||||||
|
|
||||||
|
## Onboard Smoke Test (Ubuntu + npm only)
|
||||||
|
|
||||||
|
Use this when you want to mimic a fresh machine that only has Ubuntu + npm and verify:
|
||||||
|
|
||||||
|
- `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:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/docker-onboard-smoke.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ summary: Guide to building a custom adapter
|
|||||||
|
|
||||||
Build a custom adapter to connect Paperclip to any agent runtime.
|
Build a custom adapter to connect Paperclip to any agent runtime.
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
If you're using Claude Code, the `create-agent-adapter` skill can guide you through the full adapter creation process interactively. Just ask Claude to create a new adapter and it will walk you through each step.
|
||||||
|
</Tip>
|
||||||
|
|
||||||
## Package Structure
|
## Package Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -5,24 +5,15 @@ summary: Get Paperclip running in minutes
|
|||||||
|
|
||||||
Get Paperclip running locally in under 5 minutes.
|
Get Paperclip running locally in under 5 minutes.
|
||||||
|
|
||||||
## Option 1: Docker Compose (Recommended)
|
## Quick Start (Recommended)
|
||||||
|
|
||||||
The fastest way to start. No Node.js install needed.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker compose -f docker-compose.quickstart.yml up --build
|
npx paperclipai onboard --yes
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3100](http://localhost:3100). That's it.
|
This walks you through setup, configures your environment, and gets Paperclip running.
|
||||||
|
|
||||||
The Docker image includes Claude Code CLI and Codex CLI pre-installed for local adapter runs. Pass API keys to enable them:
|
## Local Development
|
||||||
|
|
||||||
```sh
|
|
||||||
ANTHROPIC_API_KEY=sk-... OPENAI_API_KEY=sk-... \
|
|
||||||
docker compose -f docker-compose.quickstart.yml up --build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Option 2: Local Development
|
|
||||||
|
|
||||||
Prerequisites: Node.js 20+ and pnpm 9+.
|
Prerequisites: Node.js 20+ and pnpm 9+.
|
||||||
|
|
||||||
@@ -33,9 +24,9 @@ pnpm dev
|
|||||||
|
|
||||||
This starts the API server and UI at [http://localhost:3100](http://localhost:3100).
|
This starts the API server and UI at [http://localhost:3100](http://localhost:3100).
|
||||||
|
|
||||||
No Docker or external database required — Paperclip uses an embedded PostgreSQL instance by default.
|
No external database required — Paperclip uses an embedded PostgreSQL instance by default.
|
||||||
|
|
||||||
## Option 3: One-Command Bootstrap
|
## One-Command Bootstrap
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm paperclipai run
|
pnpm paperclipai run
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# @paperclipai/adapter-utils
|
# @paperclipai/adapter-utils
|
||||||
|
|
||||||
|
## 0.2.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
|
||||||
|
## 0.2.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@paperclipai/adapter-utils",
|
"name": "@paperclipai/adapter-utils",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
# @paperclipai/adapter-claude-local
|
# @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
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/adapter-utils@0.2.5
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/adapter-utils@0.2.4
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@paperclipai/adapter-claude-local",
|
"name": "@paperclipai/adapter-claude-local",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
@@ -32,7 +32,8 @@
|
|||||||
"types": "./dist/index.d.ts"
|
"types": "./dist/index.d.ts"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist",
|
||||||
|
"skills"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
|||||||
@@ -27,10 +27,19 @@ import {
|
|||||||
isClaudeUnknownSessionError,
|
isClaudeUnknownSessionError,
|
||||||
} from "./parse.js";
|
} from "./parse.js";
|
||||||
|
|
||||||
const PAPERCLIP_SKILLS_DIR = path.resolve(
|
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
path.dirname(fileURLToPath(import.meta.url)),
|
const PAPERCLIP_SKILLS_CANDIDATES = [
|
||||||
"../../../../../skills",
|
path.resolve(__moduleDir, "../../skills"), // published: <pkg>/dist/server/ -> <pkg>/skills/
|
||||||
);
|
path.resolve(__moduleDir, "../../../../../skills"), // dev: src/server/ -> repo root/skills/
|
||||||
|
];
|
||||||
|
|
||||||
|
async function resolvePaperclipSkillsDir(): Promise<string | null> {
|
||||||
|
for (const candidate of PAPERCLIP_SKILLS_CANDIDATES) {
|
||||||
|
const isDir = await fs.stat(candidate).then((s) => s.isDirectory()).catch(() => false);
|
||||||
|
if (isDir) return candidate;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a tmpdir with `.claude/skills/` containing symlinks to skills from
|
* Create a tmpdir with `.claude/skills/` containing symlinks to skills from
|
||||||
@@ -41,11 +50,13 @@ async function buildSkillsDir(): Promise<string> {
|
|||||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-skills-"));
|
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-skills-"));
|
||||||
const target = path.join(tmp, ".claude", "skills");
|
const target = path.join(tmp, ".claude", "skills");
|
||||||
await fs.mkdir(target, { recursive: true });
|
await fs.mkdir(target, { recursive: true });
|
||||||
const entries = await fs.readdir(PAPERCLIP_SKILLS_DIR, { withFileTypes: true });
|
const skillsDir = await resolvePaperclipSkillsDir();
|
||||||
|
if (!skillsDir) return tmp;
|
||||||
|
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
await fs.symlink(
|
await fs.symlink(
|
||||||
path.join(PAPERCLIP_SKILLS_DIR, entry.name),
|
path.join(skillsDir, entry.name),
|
||||||
path.join(target, entry.name),
|
path.join(target, entry.name),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
# @paperclipai/adapter-codex-local
|
# @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
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/adapter-utils@0.2.5
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/adapter-utils@0.2.4
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@paperclipai/adapter-codex-local",
|
"name": "@paperclipai/adapter-codex-local",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
@@ -32,7 +32,8 @@
|
|||||||
"types": "./dist/index.d.ts"
|
"types": "./dist/index.d.ts"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist",
|
||||||
|
"skills"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import {
|
|||||||
} from "@paperclipai/adapter-utils/server-utils";
|
} from "@paperclipai/adapter-utils/server-utils";
|
||||||
import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js";
|
import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js";
|
||||||
|
|
||||||
const PAPERCLIP_SKILLS_DIR = path.resolve(
|
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
path.dirname(fileURLToPath(import.meta.url)),
|
const PAPERCLIP_SKILLS_CANDIDATES = [
|
||||||
"../../../../../skills",
|
path.resolve(__moduleDir, "../../skills"), // published: <pkg>/dist/server/ -> <pkg>/skills/
|
||||||
);
|
path.resolve(__moduleDir, "../../../../../skills"), // dev: src/server/ -> repo root/skills/
|
||||||
|
];
|
||||||
const CODEX_ROLLOUT_NOISE_RE =
|
const CODEX_ROLLOUT_NOISE_RE =
|
||||||
/^\d{4}-\d{2}-\d{2}T[^\s]+\s+ERROR\s+codex_core::rollout::list:\s+state db missing rollout path for thread\s+[a-z0-9-]+$/i;
|
/^\d{4}-\d{2}-\d{2}T[^\s]+\s+ERROR\s+codex_core::rollout::list:\s+state db missing rollout path for thread\s+[a-z0-9-]+$/i;
|
||||||
|
|
||||||
@@ -66,19 +67,24 @@ function codexHomeDir(): string {
|
|||||||
return path.join(os.homedir(), ".codex");
|
return path.join(os.homedir(), ".codex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolvePaperclipSkillsDir(): Promise<string | null> {
|
||||||
|
for (const candidate of PAPERCLIP_SKILLS_CANDIDATES) {
|
||||||
|
const isDir = await fs.stat(candidate).then((s) => s.isDirectory()).catch(() => false);
|
||||||
|
if (isDir) return candidate;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
async function ensureCodexSkillsInjected(onLog: AdapterExecutionContext["onLog"]) {
|
async function ensureCodexSkillsInjected(onLog: AdapterExecutionContext["onLog"]) {
|
||||||
const sourceExists = await fs
|
const skillsDir = await resolvePaperclipSkillsDir();
|
||||||
.stat(PAPERCLIP_SKILLS_DIR)
|
if (!skillsDir) return;
|
||||||
.then((stats) => stats.isDirectory())
|
|
||||||
.catch(() => false);
|
|
||||||
if (!sourceExists) return;
|
|
||||||
|
|
||||||
const skillsHome = path.join(codexHomeDir(), "skills");
|
const skillsHome = path.join(codexHomeDir(), "skills");
|
||||||
await fs.mkdir(skillsHome, { recursive: true });
|
await fs.mkdir(skillsHome, { recursive: true });
|
||||||
const entries = await fs.readdir(PAPERCLIP_SKILLS_DIR, { withFileTypes: true });
|
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.isDirectory()) continue;
|
if (!entry.isDirectory()) continue;
|
||||||
const source = path.join(PAPERCLIP_SKILLS_DIR, entry.name);
|
const source = path.join(skillsDir, entry.name);
|
||||||
const target = path.join(skillsHome, entry.name);
|
const target = path.join(skillsHome, entry.name);
|
||||||
const existing = await fs.lstat(target).catch(() => null);
|
const existing = await fs.lstat(target).catch(() => null);
|
||||||
if (existing) continue;
|
if (existing) continue;
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
# @paperclipai/adapter-openclaw
|
# @paperclipai/adapter-openclaw
|
||||||
|
|
||||||
|
## 0.2.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/adapter-utils@0.2.6
|
||||||
|
|
||||||
|
## 0.2.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/adapter-utils@0.2.5
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/adapter-utils@0.2.4
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@paperclipai/adapter-openclaw",
|
"name": "@paperclipai/adapter-openclaw",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
# @paperclipai/db
|
# @paperclipai/db
|
||||||
|
|
||||||
|
## 0.2.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/shared@0.2.6
|
||||||
|
|
||||||
|
## 0.2.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/shared@0.2.5
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/shared@0.2.4
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@paperclipai/db",
|
"name": "@paperclipai/db",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# @paperclipai/shared
|
# @paperclipai/shared
|
||||||
|
|
||||||
|
## 0.2.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
|
||||||
|
## 0.2.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@paperclipai/shared",
|
"name": "@paperclipai/shared",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|||||||
36
pnpm-lock.yaml
generated
36
pnpm-lock.yaml
generated
@@ -174,7 +174,7 @@ importers:
|
|||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/shared
|
version: link:../packages/shared
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: ^1.3.8
|
specifier: 1.4.18
|
||||||
version: 1.4.18(drizzle-kit@0.31.9)(drizzle-orm@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))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
|
version: 1.4.18(drizzle-kit@0.31.9)(drizzle-orm@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))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
|
||||||
detect-port:
|
detect-port:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
@@ -185,6 +185,9 @@ importers:
|
|||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.38.4
|
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)
|
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:
|
express:
|
||||||
specifier: ^5.1.0
|
specifier: ^5.1.0
|
||||||
version: 5.2.1
|
version: 5.2.1
|
||||||
@@ -209,10 +212,6 @@ importers:
|
|||||||
zod:
|
zod:
|
||||||
specifier: ^3.24.2
|
specifier: ^3.24.2
|
||||||
version: 3.25.76
|
version: 3.25.76
|
||||||
optionalDependencies:
|
|
||||||
embedded-postgres:
|
|
||||||
specifier: ^18.1.0-beta.16
|
|
||||||
version: 18.1.0-beta.16
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/express':
|
'@types/express':
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
@@ -8285,8 +8284,7 @@ snapshots:
|
|||||||
|
|
||||||
assertion-error@2.0.1: {}
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
async-exit-hook@2.0.1:
|
async-exit-hook@2.0.1: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
asynckit@0.4.0: {}
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
@@ -8615,7 +8613,6 @@ snapshots:
|
|||||||
'@embedded-postgres/windows-x64': 18.1.0-beta.16
|
'@embedded-postgres/windows-x64': 18.1.0-beta.16
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- pg-native
|
- pg-native
|
||||||
optional: true
|
|
||||||
|
|
||||||
encodeurl@2.0.0: {}
|
encodeurl@2.0.0: {}
|
||||||
|
|
||||||
@@ -9808,19 +9805,15 @@ snapshots:
|
|||||||
pg-cloudflare@1.3.0:
|
pg-cloudflare@1.3.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
pg-connection-string@2.11.0:
|
pg-connection-string@2.11.0: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
pg-int8@1.0.1:
|
pg-int8@1.0.1: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
pg-pool@3.11.0(pg@8.18.0):
|
pg-pool@3.11.0(pg@8.18.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
pg: 8.18.0
|
pg: 8.18.0
|
||||||
optional: true
|
|
||||||
|
|
||||||
pg-protocol@1.11.0:
|
pg-protocol@1.11.0: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
pg-types@2.2.0:
|
pg-types@2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9829,7 +9822,6 @@ snapshots:
|
|||||||
postgres-bytea: 1.0.1
|
postgres-bytea: 1.0.1
|
||||||
postgres-date: 1.0.7
|
postgres-date: 1.0.7
|
||||||
postgres-interval: 1.2.0
|
postgres-interval: 1.2.0
|
||||||
optional: true
|
|
||||||
|
|
||||||
pg@8.18.0:
|
pg@8.18.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9840,12 +9832,10 @@ snapshots:
|
|||||||
pgpass: 1.0.5
|
pgpass: 1.0.5
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
pg-cloudflare: 1.3.0
|
pg-cloudflare: 1.3.0
|
||||||
optional: true
|
|
||||||
|
|
||||||
pgpass@1.0.5:
|
pgpass@1.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
split2: 4.2.0
|
split2: 4.2.0
|
||||||
optional: true
|
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
@@ -9913,19 +9903,15 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
postgres-array@2.0.0:
|
postgres-array@2.0.0: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
postgres-bytea@1.0.1:
|
postgres-bytea@1.0.1: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
postgres-date@1.0.7:
|
postgres-date@1.0.7: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
postgres-interval@1.2.0:
|
postgres-interval@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
xtend: 4.0.2
|
xtend: 4.0.2
|
||||||
optional: true
|
|
||||||
|
|
||||||
postgres@3.4.8: {}
|
postgres@3.4.8: {}
|
||||||
|
|
||||||
|
|||||||
42
scripts/docker-onboard-smoke.sh
Executable file
42
scripts/docker-onboard-smoke.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-paperclip-onboard-smoke}"
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
@@ -138,7 +138,13 @@ pnpm --filter @paperclipai/server build
|
|||||||
pnpm --filter @paperclipai/ui build
|
pnpm --filter @paperclipai/ui build
|
||||||
rm -rf "$REPO_ROOT/server/ui-dist"
|
rm -rf "$REPO_ROOT/server/ui-dist"
|
||||||
cp -r "$REPO_ROOT/ui/dist" "$REPO_ROOT/server/ui-dist"
|
cp -r "$REPO_ROOT/ui/dist" "$REPO_ROOT/server/ui-dist"
|
||||||
echo " ✓ All packages built (including UI)"
|
|
||||||
|
# Bundle skills into packages that need them (adapters + server)
|
||||||
|
for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-local; do
|
||||||
|
rm -rf "$REPO_ROOT/$pkg_dir/skills"
|
||||||
|
cp -r "$REPO_ROOT/skills" "$REPO_ROOT/$pkg_dir/skills"
|
||||||
|
done
|
||||||
|
echo " ✓ All packages built (including UI + skills)"
|
||||||
|
|
||||||
# ── Step 5: Build CLI bundle ─────────────────────────────────────────────────
|
# ── Step 5: Build CLI bundle ─────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -188,16 +194,18 @@ if [ -f "$CLI_DIR/README.md" ]; then
|
|||||||
rm "$CLI_DIR/README.md"
|
rm "$CLI_DIR/README.md"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove UI dist bundled into server for publishing
|
# Remove temporary build artifacts before committing (these are only needed during publish)
|
||||||
rm -rf "$REPO_ROOT/server/ui-dist"
|
rm -rf "$REPO_ROOT/server/ui-dist"
|
||||||
|
for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-local; do
|
||||||
|
rm -rf "$REPO_ROOT/$pkg_dir/skills"
|
||||||
|
done
|
||||||
|
|
||||||
# Stage only release-related files (avoid sweeping unrelated changes with -A)
|
# Stage only release-related files (avoid sweeping unrelated changes with -A)
|
||||||
git add \
|
git add \
|
||||||
.changeset/ \
|
.changeset/ \
|
||||||
'**/CHANGELOG.md' \
|
'**/CHANGELOG.md' \
|
||||||
'**/package.json' \
|
'**/package.json' \
|
||||||
cli/src/index.ts \
|
cli/src/index.ts
|
||||||
server/ui-dist
|
|
||||||
git commit -m "chore: release v$NEW_VERSION"
|
git commit -m "chore: release v$NEW_VERSION"
|
||||||
git tag "v$NEW_VERSION"
|
git tag "v$NEW_VERSION"
|
||||||
echo " ✓ Committed and tagged v$NEW_VERSION"
|
echo " ✓ Committed and tagged v$NEW_VERSION"
|
||||||
|
|||||||
@@ -1,5 +1,44 @@
|
|||||||
# @paperclipai/server
|
# @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
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/shared@0.2.5
|
||||||
|
- @paperclipai/adapter-utils@0.2.5
|
||||||
|
- @paperclipai/db@0.2.5
|
||||||
|
- @paperclipai/adapter-claude-local@0.2.5
|
||||||
|
- @paperclipai/adapter-codex-local@0.2.5
|
||||||
|
- @paperclipai/adapter-openclaw@0.2.5
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Version bump (patch)
|
||||||
|
- Updated dependencies
|
||||||
|
- @paperclipai/shared@0.2.4
|
||||||
|
- @paperclipai/adapter-utils@0.2.4
|
||||||
|
- @paperclipai/db@0.2.4
|
||||||
|
- @paperclipai/adapter-claude-local@0.2.4
|
||||||
|
- @paperclipai/adapter-codex-local@0.2.4
|
||||||
|
- @paperclipai/adapter-openclaw@0.2.4
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@paperclipai/server",
|
"name": "@paperclipai/server",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"ui-dist"
|
"ui-dist",
|
||||||
|
"skills"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx src/index.ts",
|
"dev": "tsx src/index.ts",
|
||||||
@@ -36,10 +37,11 @@
|
|||||||
"@paperclipai/adapter-utils": "workspace:*",
|
"@paperclipai/adapter-utils": "workspace:*",
|
||||||
"@paperclipai/db": "workspace:*",
|
"@paperclipai/db": "workspace:*",
|
||||||
"@paperclipai/shared": "workspace:*",
|
"@paperclipai/shared": "workspace:*",
|
||||||
"better-auth": "^1.3.8",
|
"better-auth": "1.4.18",
|
||||||
"detect-port": "^2.1.0",
|
"detect-port": "^2.1.0",
|
||||||
"dotenv": "^17.0.1",
|
"dotenv": "^17.0.1",
|
||||||
"drizzle-orm": "^0.38.4",
|
"drizzle-orm": "^0.38.4",
|
||||||
|
"embedded-postgres": "^18.1.0-beta.16",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.0.2",
|
||||||
"open": "^11.0.0",
|
"open": "^11.0.0",
|
||||||
@@ -49,9 +51,6 @@
|
|||||||
"ws": "^8.19.0",
|
"ws": "^8.19.0",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
|
||||||
"embedded-postgres": "^18.1.0-beta.16"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/express-serve-static-core": "^5.0.0",
|
"@types/express-serve-static-core": "^5.0.0",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { createServer } from "node:http";
|
|||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { createInterface } from "node:readline/promises";
|
import { createInterface } from "node:readline/promises";
|
||||||
import { stdin, stdout } from "node:process";
|
import { stdin, stdout } from "node:process";
|
||||||
import type { Request as ExpressRequest } from "express";
|
import type { Request as ExpressRequest, RequestHandler } from "express";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
createDb,
|
createDb,
|
||||||
@@ -26,12 +26,17 @@ import { heartbeatService } from "./services/index.js";
|
|||||||
import { createStorageServiceFromConfig } from "./storage/index.js";
|
import { createStorageServiceFromConfig } from "./storage/index.js";
|
||||||
import { printStartupBanner } from "./startup-banner.js";
|
import { printStartupBanner } from "./startup-banner.js";
|
||||||
import { getBoardClaimWarningUrl, initializeBoardClaimChallenge } from "./board-claim.js";
|
import { getBoardClaimWarningUrl, initializeBoardClaimChallenge } from "./board-claim.js";
|
||||||
import {
|
|
||||||
createBetterAuthHandler,
|
type BetterAuthSessionUser = {
|
||||||
createBetterAuthInstance,
|
id: string;
|
||||||
resolveBetterAuthSession,
|
email?: string | null;
|
||||||
resolveBetterAuthSessionFromHeaders,
|
name?: string | null;
|
||||||
} from "./auth/better-auth.js";
|
};
|
||||||
|
|
||||||
|
type BetterAuthSessionResult = {
|
||||||
|
session: { id: string; userId: string } | null;
|
||||||
|
user: BetterAuthSessionUser | null;
|
||||||
|
};
|
||||||
|
|
||||||
type EmbeddedPostgresInstance = {
|
type EmbeddedPostgresInstance = {
|
||||||
initialise(): Promise<void>;
|
initialise(): Promise<void>;
|
||||||
@@ -232,7 +237,7 @@ if (config.databaseUrl) {
|
|||||||
EmbeddedPostgres = mod.default as EmbeddedPostgresCtor;
|
EmbeddedPostgres = mod.default as EmbeddedPostgresCtor;
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(
|
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.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,17 +393,23 @@ if (config.deploymentMode === "authenticated") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let authReady = config.deploymentMode === "local_trusted";
|
let authReady = config.deploymentMode === "local_trusted";
|
||||||
let betterAuthHandler: ReturnType<typeof createBetterAuthHandler> | undefined;
|
let betterAuthHandler: RequestHandler | undefined;
|
||||||
let resolveSession:
|
let resolveSession:
|
||||||
| ((req: ExpressRequest) => Promise<Awaited<ReturnType<typeof resolveBetterAuthSession>>>)
|
| ((req: ExpressRequest) => Promise<BetterAuthSessionResult | null>)
|
||||||
| undefined;
|
| undefined;
|
||||||
let resolveSessionFromHeaders:
|
let resolveSessionFromHeaders:
|
||||||
| ((headers: Headers) => Promise<Awaited<ReturnType<typeof resolveBetterAuthSession>>>)
|
| ((headers: Headers) => Promise<BetterAuthSessionResult | null>)
|
||||||
| undefined;
|
| undefined;
|
||||||
if (config.deploymentMode === "local_trusted") {
|
if (config.deploymentMode === "local_trusted") {
|
||||||
await ensureLocalTrustedBoardPrincipal(db as any);
|
await ensureLocalTrustedBoardPrincipal(db as any);
|
||||||
}
|
}
|
||||||
if (config.deploymentMode === "authenticated") {
|
if (config.deploymentMode === "authenticated") {
|
||||||
|
const {
|
||||||
|
createBetterAuthHandler,
|
||||||
|
createBetterAuthInstance,
|
||||||
|
resolveBetterAuthSession,
|
||||||
|
resolveBetterAuthSessionFromHeaders,
|
||||||
|
} = await import("./auth/better-auth.js");
|
||||||
const betterAuthSecret =
|
const betterAuthSecret =
|
||||||
process.env.BETTER_AUTH_SECRET?.trim() ?? process.env.PAPERCLIP_AGENT_JWT_SECRET?.trim();
|
process.env.BETTER_AUTH_SECRET?.trim() ?? process.env.PAPERCLIP_AGENT_JWT_SECRET?.trim();
|
||||||
if (!betterAuthSecret) {
|
if (!betterAuthSecret) {
|
||||||
|
|||||||
@@ -59,8 +59,9 @@ function readSkillMarkdown(skillName: string): string | null {
|
|||||||
if (normalized !== "paperclip" && normalized !== "paperclip-create-agent") return null;
|
if (normalized !== "paperclip" && normalized !== "paperclip-create-agent") return null;
|
||||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const candidates = [
|
const candidates = [
|
||||||
path.resolve(process.cwd(), "skills", normalized, "SKILL.md"),
|
path.resolve(moduleDir, "../../skills", normalized, "SKILL.md"), // published: dist/routes/ -> <pkg>/skills/
|
||||||
path.resolve(moduleDir, "../../../skills", normalized, "SKILL.md"),
|
path.resolve(process.cwd(), "skills", normalized, "SKILL.md"), // cwd (e.g. monorepo root)
|
||||||
|
path.resolve(moduleDir, "../../../skills", normalized, "SKILL.md"), // dev: src/routes/ -> repo root/skills/
|
||||||
];
|
];
|
||||||
for (const skillPath of candidates) {
|
for (const skillPath of candidates) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -207,6 +207,17 @@ PATCH /api/agents/{agentId}/instructions-path
|
|||||||
| Release task | `POST /api/issues/:issueId/release` |
|
| Release task | `POST /api/issues/:issueId/release` |
|
||||||
| List agents | `GET /api/companies/:companyId/agents` |
|
| List agents | `GET /api/companies/:companyId/agents` |
|
||||||
| Dashboard | `GET /api/companies/:companyId/dashboard` |
|
| 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
|
## Full Reference
|
||||||
|
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ Terminal states: `done`, `cancelled`
|
|||||||
|
|
||||||
| Method | Path | Description |
|
| 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 |
|
| GET | `/api/issues/:issueId` | Issue details + ancestors |
|
||||||
| POST | `/api/companies/:companyId/issues` | Create issue |
|
| POST | `/api/companies/:companyId/issues` | Create issue |
|
||||||
| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field adds a comment in same call) |
|
| 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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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",
|
theme === "dark" && "prose-invert",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ export const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>
|
|||||||
onBlur={() => onBlur?.()}
|
onBlur={() => onBlur?.()}
|
||||||
className={cn("paperclip-mdxeditor", !bordered && "paperclip-mdxeditor--borderless")}
|
className={cn("paperclip-mdxeditor", !bordered && "paperclip-mdxeditor--borderless")}
|
||||||
contentEditableClassName={cn(
|
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,
|
contentClassName,
|
||||||
)}
|
)}
|
||||||
overlayContainer={containerRef.current}
|
overlayContainer={containerRef.current}
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ export function NewIssueDialog() {
|
|||||||
</button>
|
</button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-48 p-1" align="start">
|
<PopoverContent className="w-48 p-1" align="start">
|
||||||
{companies.map((c) => (
|
{companies.filter((c) => c.status !== "archived").map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.id}
|
key={c.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ export function OnboardingWizard() {
|
|||||||
useState<AdapterEnvironmentTestResult | null>(null);
|
useState<AdapterEnvironmentTestResult | null>(null);
|
||||||
const [adapterEnvError, setAdapterEnvError] = useState<string | null>(null);
|
const [adapterEnvError, setAdapterEnvError] = useState<string | null>(null);
|
||||||
const [adapterEnvLoading, setAdapterEnvLoading] = useState(false);
|
const [adapterEnvLoading, setAdapterEnvLoading] = useState(false);
|
||||||
|
const [forceUnsetAnthropicApiKey, setForceUnsetAnthropicApiKey] =
|
||||||
|
useState(false);
|
||||||
|
const [unsetAnthropicLoading, setUnsetAnthropicLoading] = useState(false);
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
const [taskTitle, setTaskTitle] = useState("Create your CEO HEARTBEAT.md");
|
const [taskTitle, setTaskTitle] = useState("Create your CEO HEARTBEAT.md");
|
||||||
@@ -159,6 +162,15 @@ export function OnboardingWizard() {
|
|||||||
}, [step, adapterType, cwd, model, command, args, url]);
|
}, [step, adapterType, cwd, model, command, args, url]);
|
||||||
|
|
||||||
const selectedModel = (adapterModels ?? []).find((m) => m.id === model);
|
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() {
|
function reset() {
|
||||||
setStep(1);
|
setStep(1);
|
||||||
@@ -176,6 +188,8 @@ export function OnboardingWizard() {
|
|||||||
setAdapterEnvResult(null);
|
setAdapterEnvResult(null);
|
||||||
setAdapterEnvError(null);
|
setAdapterEnvError(null);
|
||||||
setAdapterEnvLoading(false);
|
setAdapterEnvLoading(false);
|
||||||
|
setForceUnsetAnthropicApiKey(false);
|
||||||
|
setUnsetAnthropicLoading(false);
|
||||||
setTaskTitle("Create your CEO HEARTBEAT.md");
|
setTaskTitle("Create your CEO HEARTBEAT.md");
|
||||||
setTaskDescription(DEFAULT_TASK_DESCRIPTION);
|
setTaskDescription(DEFAULT_TASK_DESCRIPTION);
|
||||||
setCreatedCompanyId(null);
|
setCreatedCompanyId(null);
|
||||||
@@ -191,7 +205,7 @@ export function OnboardingWizard() {
|
|||||||
|
|
||||||
function buildAdapterConfig(): Record<string, unknown> {
|
function buildAdapterConfig(): Record<string, unknown> {
|
||||||
const adapter = getUIAdapter(adapterType);
|
const adapter = getUIAdapter(adapterType);
|
||||||
return adapter.buildAdapterConfig({
|
const config = adapter.buildAdapterConfig({
|
||||||
...defaultCreateValues,
|
...defaultCreateValues,
|
||||||
adapterType,
|
adapterType,
|
||||||
cwd,
|
cwd,
|
||||||
@@ -208,9 +222,22 @@ export function OnboardingWizard() {
|
|||||||
? DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX
|
? DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX
|
||||||
: defaultCreateValues.dangerouslyBypassSandbox
|
: 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) {
|
if (!createdCompanyId) {
|
||||||
setAdapterEnvError(
|
setAdapterEnvError(
|
||||||
"Create or select a company before testing adapter environment."
|
"Create or select a company before testing adapter environment."
|
||||||
@@ -224,7 +251,7 @@ export function OnboardingWizard() {
|
|||||||
createdCompanyId,
|
createdCompanyId,
|
||||||
adapterType,
|
adapterType,
|
||||||
{
|
{
|
||||||
adapterConfig: buildAdapterConfig()
|
adapterConfig: adapterConfigOverride ?? buildAdapterConfig()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
setAdapterEnvResult(result);
|
setAdapterEnvResult(result);
|
||||||
@@ -276,12 +303,6 @@ export function OnboardingWizard() {
|
|||||||
if (isLocalAdapter) {
|
if (isLocalAdapter) {
|
||||||
const result = adapterEnvResult ?? (await runAdapterEnvironmentTest());
|
const result = adapterEnvResult ?? (await runAdapterEnvironmentTest());
|
||||||
if (!result) return;
|
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, {
|
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() {
|
async function handleStep3Next() {
|
||||||
if (!createdCompanyId || !createdAgentId) return;
|
if (!createdCompanyId || !createdAgentId) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -673,6 +743,24 @@ export function OnboardingWizard() {
|
|||||||
<AdapterEnvironmentResult result={adapterEnvResult} />
|
<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">
|
<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="font-medium">Manual debug</p>
|
||||||
<p className="text-muted-foreground font-mono break-all">
|
<p className="text-muted-foreground font-mono break-all">
|
||||||
|
|||||||
Reference in New Issue
Block a user