diff --git a/packages/adapters/claude-local/package.json b/packages/adapters/claude-local/package.json index 536ee2f4..22d71b19 100644 --- a/packages/adapters/claude-local/package.json +++ b/packages/adapters/claude-local/package.json @@ -1,6 +1,6 @@ { "name": "@paperclipai/adapter-claude-local", - "version": "0.2.3", + "version": "0.2.4", "type": "module", "exports": { ".": "./src/index.ts", @@ -32,7 +32,8 @@ "types": "./dist/index.d.ts" }, "files": [ - "dist" + "dist", + "skills" ], "scripts": { "build": "tsc", diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index 9827544d..32fa6bd4 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -27,10 +27,19 @@ import { isClaudeUnknownSessionError, } from "./parse.js"; -const PAPERCLIP_SKILLS_DIR = path.resolve( - path.dirname(fileURLToPath(import.meta.url)), - "../../../../../skills", -); +const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); +const PAPERCLIP_SKILLS_CANDIDATES = [ + path.resolve(__moduleDir, "../../skills"), // published: /dist/server/ -> /skills/ + path.resolve(__moduleDir, "../../../../../skills"), // dev: src/server/ -> repo root/skills/ +]; + +async function resolvePaperclipSkillsDir(): Promise { + 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 @@ -41,11 +50,13 @@ async function buildSkillsDir(): Promise { const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-skills-")); const target = path.join(tmp, ".claude", "skills"); 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) { if (entry.isDirectory()) { await fs.symlink( - path.join(PAPERCLIP_SKILLS_DIR, entry.name), + path.join(skillsDir, entry.name), path.join(target, entry.name), ); } diff --git a/packages/adapters/codex-local/package.json b/packages/adapters/codex-local/package.json index e34947fc..4a99e9a4 100644 --- a/packages/adapters/codex-local/package.json +++ b/packages/adapters/codex-local/package.json @@ -1,6 +1,6 @@ { "name": "@paperclipai/adapter-codex-local", - "version": "0.2.3", + "version": "0.2.4", "type": "module", "exports": { ".": "./src/index.ts", @@ -32,7 +32,8 @@ "types": "./dist/index.d.ts" }, "files": [ - "dist" + "dist", + "skills" ], "scripts": { "build": "tsc", diff --git a/packages/adapters/codex-local/src/server/execute.ts b/packages/adapters/codex-local/src/server/execute.ts index 47ce9e96..f9d871c9 100644 --- a/packages/adapters/codex-local/src/server/execute.ts +++ b/packages/adapters/codex-local/src/server/execute.ts @@ -19,10 +19,11 @@ import { } from "@paperclipai/adapter-utils/server-utils"; import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js"; -const PAPERCLIP_SKILLS_DIR = path.resolve( - path.dirname(fileURLToPath(import.meta.url)), - "../../../../../skills", -); +const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); +const PAPERCLIP_SKILLS_CANDIDATES = [ + path.resolve(__moduleDir, "../../skills"), // published: /dist/server/ -> /skills/ + path.resolve(__moduleDir, "../../../../../skills"), // dev: src/server/ -> repo root/skills/ +]; 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; @@ -66,19 +67,24 @@ function codexHomeDir(): string { return path.join(os.homedir(), ".codex"); } +async function resolvePaperclipSkillsDir(): Promise { + 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"]) { - const sourceExists = await fs - .stat(PAPERCLIP_SKILLS_DIR) - .then((stats) => stats.isDirectory()) - .catch(() => false); - if (!sourceExists) return; + const skillsDir = await resolvePaperclipSkillsDir(); + if (!skillsDir) return; const skillsHome = path.join(codexHomeDir(), "skills"); 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) { 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 existing = await fs.lstat(target).catch(() => null); if (existing) continue; diff --git a/scripts/release.sh b/scripts/release.sh index 04d30347..4a3bbec8 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -138,7 +138,13 @@ pnpm --filter @paperclipai/server build pnpm --filter @paperclipai/ui build rm -rf "$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 ───────────────────────────────────────────────── @@ -191,6 +197,11 @@ fi # Remove UI dist bundled into server for publishing rm -rf "$REPO_ROOT/server/ui-dist" +# Remove skills bundled into packages for publishing +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) git add \ .changeset/ \ diff --git a/server/package.json b/server/package.json index db67e938..379ac229 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@paperclipai/server", - "version": "0.2.3", + "version": "0.2.4", "type": "module", "exports": { ".": "./src/index.ts" @@ -18,7 +18,8 @@ }, "files": [ "dist", - "ui-dist" + "ui-dist", + "skills" ], "scripts": { "dev": "tsx src/index.ts", diff --git a/server/src/routes/access.ts b/server/src/routes/access.ts index 64e1b881..158dd2df 100644 --- a/server/src/routes/access.ts +++ b/server/src/routes/access.ts @@ -59,8 +59,9 @@ function readSkillMarkdown(skillName: string): string | null { if (normalized !== "paperclip" && normalized !== "paperclip-create-agent") return null; const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const candidates = [ - path.resolve(process.cwd(), "skills", normalized, "SKILL.md"), - path.resolve(moduleDir, "../../../skills", normalized, "SKILL.md"), + path.resolve(moduleDir, "../../skills", normalized, "SKILL.md"), // published: dist/routes/ -> /skills/ + 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) { try {