skills/paperclip-plugin-dev/SKILL.md
Build, publish, and install Paperclip plugins correctly. This skill should be used when scaffolding a new Paperclip plugin, writing a plugin manifest, implementing plugin worker logic, adding UI slots, publishing to npm, or installing a plugin into a Paperclip instance. Contains critical lessons from real publishing failures. Also invoke when working on plugin capabilities, jobs, webhooks, agent tools, or the plugin SDK.
npx skillsauth add b-open-io/prompts paperclip-plugin-devInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build Paperclip plugins based on the actual SDK source code, validator source code, and examples. Follows lessons learned from real publishing failures.
These mistakes have cost real time. Do not repeat them.
files field in package.json is REQUIREDnpm uses .gitignore to exclude files. Since dist/ is gitignored, built output will be absent unless explicitly declared:
{ "files": ["dist", "package.json"] }
The server rejects manifests where features lack matching capabilities. Every UI slot type, tool, job, and webhook requires a specific capability. See references/manifest-reference.md for the full mapping table.
Example: declaring a dashboardWidget slot without ui.dashboardWidget.register in capabilities causes install failure.
0.0.1The scaffold generates 0.1.0. Change to 0.0.1 before first publish.
paperclipPlugin fields point to ./dist/. Run bun run build before npm publish. Verify with npm pack --dry-run.
When iterating, clear stale cache on the server: npm cache clean --force via SSH.
Publishing a new version with additional capabilities puts the plugin in upgrade_pending state. Plan v1 capabilities carefully.
agents.create in SDKThe plugin SDK can read, pause, resume, invoke, and chat with agents — but cannot create or update them. For v1, present templates in the UI and let the operator create agents manually.
error.data, NOT error.messageWhen catching errors from Convex mutations in Next.js API routes, error.message is always the generic "[Request ID: xxx] Server Error". The actual user-facing message is in error.data. Use this pattern:
import { ConvexError } from "convex/values";
function extractConvexErrorMessage(error: unknown): string {
if (error instanceof ConvexError) {
const data = error.data;
if (typeof data === "string") return data;
if (data && typeof data === "object" && "message" in data) {
return String((data as { message: unknown }).message);
}
return JSON.stringify(data);
}
if (error instanceof Error) return error.message;
return String(error);
}
When upstream Paperclip adds new fields to shared types (like originKind on Issue), the plugin SDK tgz files become stale. To rebuild:
cd ~/code/paperclip/packages/shared && pnpm run build && pnpm pack
cd ~/code/paperclip/packages/plugins/sdk && pnpm run build && pnpm pack
# Copy both .tgz files to plugin's .paperclip-sdk/ directory
# Delete pnpm-lock.yaml (integrity hashes are cached)
bun install
ctx.http.fetch does NOT support relative URLs or private IPsPlugin workers cannot call the internal Paperclip API via ctx.http.fetch — it blocks private IPs and requires absolute URLs. Use the typed SDK clients instead: ctx.issues.list, ctx.agents.list, etc. There is no ctx.routines client.
node ~/code/paperclip/packages/plugins/create-paperclip-plugin/src/index.ts \
<package-name> \
--output <dir> \
--display-name "<Name>" \
--description "<text>" \
--author "<name>" \
--category connector|workspace|automation|ui \
--sdk-path ~/code/paperclip/packages/plugins/sdk
Immediately after scaffolding:
"files": ["dist", "package.json"] to package.json"0.0.1"import { definePlugin, runWorker } from "@paperclipai/plugin-sdk";
const plugin = definePlugin({
async setup(ctx) {
ctx.events.on("issue.created", async (event) => { ... });
ctx.jobs.register("my-sync", async (job) => { ... });
ctx.data.register("health", async (params) => ({ status: "ok" }));
ctx.actions.register("resync", async (params) => { ... });
ctx.tools.register("my-tool", { displayName: "...", description: "...", parametersSchema: { ... } },
async (params, runCtx) => ({ content: "result" }));
},
async onHealth() { return { status: "ok" }; },
});
export default plugin;
runWorker(plugin, import.meta.url);
For the full PluginContext API: read references/worker-api-reference.md.
import { usePluginData, usePluginAction, useHostContext, usePluginStream } from "@paperclipai/plugin-sdk/ui";
export function DashboardWidget() {
const { companyId } = useHostContext();
const { data, loading, error, refresh } = usePluginData<T>("health", { companyId });
const doAction = usePluginAction("resync");
if (loading) return <div>Loading...</div>;
return <div>Status: {data?.status}</div>;
}
For all hooks, props, patterns, and styling: read references/ui-reference.md.
[ ] bun run build
[ ] "files": ["dist", "package.json"] in package.json
[ ] Version is correct (0.0.1 for first publish)
[ ] Every declared slot type has matching capability
[ ] tools[] → "agent.tools.register", jobs[] → "jobs.schedule", webhooks[] → "webhooks.receive"
[ ] npm pack --dry-run — verify dist/ appears
[ ] bun run test passes
[ ] Use Skill(bopen-tools:npm-publish) for publishing
Enter the npm package name (e.g., @bopen-io/tortuga-plugin) in the "Install Plugin" dialog in Settings → Plugins. The server downloads from npm, validates the manifest, and starts the worker.
"<plugin-slug>-<slot-type>" — e.g., "tortuga-dashboard-widget""DashboardWidget", "FleetPage""clawnet-sync", "fleet-status"constants.ts filePlugin workers run in a vm.createContext() sandbox. No access to process, require, fs, net, child_process. CJS bundles only (esbuild presets handle this). All host interaction through PluginContext methods.
Declare format: "secret-ref" in instanceConfigSchema. Operator pastes a secret UUID. Resolve at runtime: await ctx.secrets.resolve(config.apiKey). Never cache resolved values.
For detailed API documentation, consult:
references/manifest-reference.md — All 37 capabilities, slot types, validation rules, declaration examplesreferences/worker-api-reference.md — Full PluginContext API, lifecycle hooks, sandbox constraintsreferences/ui-reference.md — All 5 hooks, component props, styling, streaming, navigation patterns~/code/paperclip/packages/plugins/sdk/~/code/paperclip/doc/plugins/PLUGIN_SPEC.md~/code/paperclip/packages/plugins/examples/plugin-kitchen-sink-example/~/code/paperclip/packages/plugins/examples/plugin-hello-world-example/~/code/paperclip/packages/plugins/examples/plugin-file-browser-example/~/code/paperclip/packages/plugins/create-paperclip-plugin/~/code/paperclip/server/src/services/plugin-capability-validator.ts~/code/paperclip/server/src/services/plugin-manifest-validator.ts~/code/tortuga-plugin/~/code/tortuga-plugin/ARCHITECTURE.mddevelopment
This skill should be used when the user asks to "design a business card", "make a printable PDF", "render HTML to PDF", "generate a postcard", "build print collateral", "set up an HTML print pipeline", or needs help with bleed, safe areas, font embedding, or QR generation for print. Provides a Playwright-based pipeline with multiple bundled templates and theme variants for business cards (minimal, watercolor light, watercolor dark) and instructions for adding new templates.
tools
Get recent tweets from an X/Twitter user. Use when user asks "what has @username posted", "recent tweets from", "user's X posts", "show timeline for", "what is @user saying". Requires X_BEARER_TOKEN.
data-ai
Get X/Twitter user profile by username. Use when user asks "who is @username", "get X profile", "lookup Twitter user", "find X account", "user details", "follower count for". Requires X_BEARER_TOKEN.
data-ai
Search recent X/Twitter posts by query. Returns RAW TWEETS (last 7 days). Use when user asks "search X for", "find tweets about", "what are people saying about", "Twitter search", "raw tweets about". For AI summaries/sentiment, use x-research instead. Requires X_BEARER_TOKEN.