From pubm-plugin
Scaffolds pubm plugins by gathering name, hooks like beforePublish/afterRelease, and form (inline config, single file, or full package) with TypeScript templates.
npx claudepluginhub syi0808/pubm --plugin pubm-pluginThis skill is limited to using the following tools:
Create a new pubm plugin, from an inline hook to a full publishable package.
Sets up pubm in JS/TS and Rust projects: analyzes structure, installs pubm, configures registries (npm, jsr, crates.io, private), and integrates CI workflows.
Publishes hook scripts to claude-code-plugin repos: validates structure/tests, checks duplicates, generates plugin.json with portable commands, registers marketplace.json. For hook publishing, moving, or packaging.
Orchestrates Claude Code plugin lifecycle: create new plugins from concepts, improve existing via assessment, research, design, creation, debugging, optimization, verification.
Share bugs, ideas, or general feedback.
Create a new pubm plugin, from an inline hook to a full publishable package.
Use AskUserQuestion to collect:
plugin- prefix). Example: slack-notify, sentry-releaseAvailable hooks (see references/plugin-api.md for details):
| Hook | When it runs |
|---|---|
beforeTest | Before test script |
afterTest | After test script |
beforeBuild | Before build script |
afterBuild | After build script |
beforeVersion | Before version bump |
afterVersion | After version bump commit |
beforePublish | Before registry publish |
afterPublish | After registry publish |
beforePush | Before git push |
afterPush | After git push |
afterRelease | After GitHub release creation (receives ReleaseContext) |
onError | On pipeline error (receives Error). Use ctx.runtime.rollback.add() to register rollback actions. |
onSuccess | On successful publish |
Use AskUserQuestion to ask which form they want:
| Form | When to use | What gets created |
|---|---|---|
| Config inline | Simple one-off hook logic | Plugin function added directly in pubm.config.ts |
| Single file | Reusable but lightweight | A single .ts file with a factory function, imported in pubm.config.ts |
| Package | Publishable, has deps/tests | Full packages/plugins/plugin-{name}/ scaffold |
Then follow the matching section below.
Add the plugin factory function and its invocation directly in pubm.config.ts. No new files are created.
import { defineConfig } from "@pubm/core";
import type { PubmPlugin } from "@pubm/core";
function {camelName}(): PubmPlugin {
return {
name: "{name}",
hooks: {
{selectedHook}: async (ctx) => {
// TODO: Implement {selectedHook} logic
},
},
};
}
export default defineConfig({
// ...existing config...
plugins: [
// ...existing plugins...
{camelName}(),
],
});
If the plugin needs options, define an inline interface above the function:
interface {PascalName}Options {
// options here
}
function {camelName}(options: {PascalName}Options): PubmPlugin {
// ...
}
After adding, skip to Step: Present Next Steps.
Create a single .ts file with the factory function. Let the user choose the file location; suggest plugins/{name}.ts or src/plugins/{name}.ts.
plugins/{name}.tsimport type { PubmPlugin } from "@pubm/core";
export interface {PascalName}Options {
// TODO: Define your plugin options here
}
export function {camelName}(options: {PascalName}Options): PubmPlugin {
return {
name: "{name}",
hooks: {
{selectedHook}: async (ctx) => {
// TODO: Implement {selectedHook} logic
},
},
};
}
If afterRelease is selected, use the special signature:
afterRelease: async (ctx, releaseCtx) => {
// releaseCtx has: { releaseUrl, tagName, releaseName }
},
If onError is selected, use the error signature:
onError: async (ctx, error) => {
// error is the Error that caused the failure
},
Then register in pubm.config.ts:
import { defineConfig } from "@pubm/core";
import { {camelName} } from "./plugins/{name}.js";
export default defineConfig({
// ...existing config...
plugins: [
// ...existing plugins...
{camelName}({ /* options */ }),
],
});
After creating, skip to Step: Present Next Steps.
Create packages/plugins/plugin-{name}/ with full boilerplate.
package.json{
"name": "@pubm/plugin-{name}",
"version": "0.0.1",
"type": "module",
"description": "{description}",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": ["dist/"],
"scripts": {
"build": "bun build src/index.ts --outdir dist --target node --format esm && bunx tsc --project tsconfig.build.json",
"check": "biome check",
"typecheck": "tsc --noEmit",
"test": "vitest --run",
"coverage": "vitest --run --coverage"
},
"peerDependencies": {
"@pubm/core": ">=0.3.6"
},
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/syi0808/pubm.git",
"directory": "packages/plugins/plugin-{name}"
}
}
tsconfig.json{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "dist",
"paths": {
"@pubm/core": ["../../core/src/index.ts"]
}
},
"references": [{ "path": "../../core" }],
"include": ["src/**/*.ts"],
"exclude": ["tests", "dist", "node_modules"]
}
tsconfig.build.json{
"extends": "./tsconfig.json",
"compilerOptions": {
"emitDeclarationOnly": true,
"declaration": true,
"declarationDir": "dist",
"paths": {}
}
}
vitest.config.mtsimport { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["tests/**/*.{test,spec}.?(c|m)[jt]s?(x)"],
setupFiles: ["tests/setup.ts"],
pool: "forks",
testTimeout: 30000,
passWithNoTests: true,
},
});
src/types.tsexport interface {PascalName}Options {
// TODO: Define your plugin options here
}
src/index.tsGenerate based on selected hooks. Pattern:
import type { PubmPlugin } from "@pubm/core";
import type { {PascalName}Options } from "./types.js";
export type { {PascalName}Options } from "./types.js";
export function {camelName}(options: {PascalName}Options): PubmPlugin {
return {
name: "{name}",
hooks: {
{selectedHook}: async (ctx) => {
// TODO: Implement {selectedHook} logic
},
},
};
}
If afterRelease is selected, use the special signature:
afterRelease: async (ctx, releaseCtx) => {
// releaseCtx has: { releaseUrl, tagName, releaseName }
},
If onError is selected, use the error signature:
onError: async (ctx, error) => {
// error is the Error that caused the failure
},
If CLI commands are needed, add a commands property:
commands: [
{
name: "{name}",
description: "{description}",
subcommands: [
{
name: "init",
description: "Initialize {name} configuration",
options: [],
action: async (args) => {
// TODO: Implement command
},
},
],
},
],
tests/setup.tsimport { afterEach, vi } from "vitest";
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
tests/unit/plugin.test.tsimport { describe, expect, it } from "vitest";
import { {camelName} } from "../../src/index.js";
describe("{name} plugin", () => {
it("should return a valid PubmPlugin", () => {
const plugin = {camelName}({});
expect(plugin.name).toBe("{name}");
expect(plugin.hooks).toBeDefined();
});
// Add hook-specific tests:
// it("should have {selectedHook} hook", () => {
// const plugin = {camelName}({});
// expect(plugin.hooks?.{selectedHook}).toBeTypeOf("function");
// });
});
Check the root package.json for the workspaces field. If it uses a glob like packages/plugins/*, the new package is already covered. Otherwise, add the plugin path.
Run bun install from the repo root to link the new package.
Run in sequence:
cd packages/plugins/plugin-{name} && bun run build && bun run test
After creation, tell the user:
tests/unit/ and tests/integration/)pubm.config.ts (if not already done):
import { defineConfig } from "@pubm/core";
import { {camelName} } from "@pubm/plugin-{name}";
export default defineConfig({
plugins: [
{camelName}({ /* options */ }),
],
});
references/plugin-api.md for the full plugin APIConvert the user-provided {name} (kebab-case) to:
slack-notify → SlackNotifyslack-notify → slackNotify@pubm/plugin-{name}packages/plugins/plugin-{name}PubmPlugin)@pubm/core as a peer dependency (Package form), never a regular dependency"type": "module")@pubm/core unless the user explicitly requests themreferences/plugin-api.md -- Complete PubmPlugin interface and hook reference