Compare commits
10 Commits
@paperclip
...
@paperclip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea637110ac | ||
|
|
3ae9d95354 | ||
|
|
a95e38485d | ||
|
|
c7c96feef7 | ||
|
|
7e387a1883 | ||
|
|
108bb9bd15 | ||
|
|
6141d5c3f2 | ||
|
|
a4da932d8d | ||
|
|
ab3b9ab19f | ||
|
|
f4a5b00116 |
29
Dockerfile.onboard-smoke
Normal file
29
Dockerfile.onboard-smoke
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ARG NODE_MAJOR=20
|
||||||
|
ARG PAPERCLIPAI_VERSION=latest
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
PAPERCLIP_HOME=/paperclip \
|
||||||
|
PAPERCLIP_OPEN_ON_LISTEN=false \
|
||||||
|
HOST=0.0.0.0 \
|
||||||
|
PORT=3100 \
|
||||||
|
NODE_MAJOR=${NODE_MAJOR} \
|
||||||
|
PAPERCLIPAI_VERSION=${PAPERCLIPAI_VERSION}
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg \
|
||||||
|
&& 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 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
VOLUME ["/paperclip"]
|
||||||
|
WORKDIR /workspace
|
||||||
|
EXPOSE 3100
|
||||||
|
|
||||||
|
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,33 @@
|
|||||||
# paperclipai
|
# paperclipai
|
||||||
|
|
||||||
|
## 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.5",
|
||||||
"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.5");
|
||||||
|
|
||||||
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,29 @@ 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
|
||||||
|
|
||||||
|
Build + run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/docker-onboard-smoke.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Open: `http://localhost:3100`
|
||||||
|
|
||||||
|
Useful overrides:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
HOST_PORT=3200 PAPERCLIPAI_VERSION=latest ./scripts/docker-onboard-smoke.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Persistent data is mounted at `./data/docker-onboard-smoke` by default.
|
||||||
|
- 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,17 @@
|
|||||||
# @paperclipai/adapter-utils
|
# @paperclipai/adapter-utils
|
||||||
|
|
||||||
|
## 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.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# @paperclipai/adapter-claude-local
|
# @paperclipai/adapter-claude-local
|
||||||
|
|
||||||
|
## 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.5",
|
||||||
"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,21 @@
|
|||||||
# @paperclipai/adapter-codex-local
|
# @paperclipai/adapter-codex-local
|
||||||
|
|
||||||
|
## 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.5",
|
||||||
"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,21 @@
|
|||||||
# @paperclipai/adapter-openclaw
|
# @paperclipai/adapter-openclaw
|
||||||
|
|
||||||
|
## 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.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# @paperclipai/db
|
# @paperclipai/db
|
||||||
|
|
||||||
|
## 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.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# @paperclipai/shared
|
# @paperclipai/shared
|
||||||
|
|
||||||
|
## 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.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|||||||
2
pnpm-lock.yaml
generated
2
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
|
||||||
|
|||||||
28
scripts/docker-onboard-smoke.sh
Executable file
28
scripts/docker-onboard-smoke.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-paperclip-onboard-smoke}"
|
||||||
|
HOST_PORT="${HOST_PORT:-3100}"
|
||||||
|
PAPERCLIPAI_VERSION="${PAPERCLIPAI_VERSION:-latest}"
|
||||||
|
DATA_DIR="${DATA_DIR:-$REPO_ROOT/data/docker-onboard-smoke}"
|
||||||
|
|
||||||
|
mkdir -p "$DATA_DIR"
|
||||||
|
|
||||||
|
echo "==> Building onboard smoke image"
|
||||||
|
docker build \
|
||||||
|
--build-arg PAPERCLIPAI_VERSION="$PAPERCLIPAI_VERSION" \
|
||||||
|
-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"
|
||||||
|
docker run --rm \
|
||||||
|
--name "${IMAGE_NAME//[^a-zA-Z0-9_.-]/-}" \
|
||||||
|
-p "$HOST_PORT:3100" \
|
||||||
|
-e HOST=0.0.0.0 \
|
||||||
|
-e PORT=3100 \
|
||||||
|
-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,31 @@
|
|||||||
# @paperclipai/server
|
# @paperclipai/server
|
||||||
|
|
||||||
|
## 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.5",
|
||||||
"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,7 +37,7 @@
|
|||||||
"@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",
|
||||||
|
|||||||
@@ -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>;
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user