Gpc plugin development
Agent skills for the GPC CLI. 19 skills that teach Claude Code how to use GPC for Google Play workflows: releases, metadata, vitals, monetization, CI/CD.
npx -y skills add yasserstudio/gpc-skills --skill gpc-plugin-developmentAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when building, extending, or debugging GPC plugins. Make sure to use this skill whenever the user mentions gpc plugins, plugin SDK, @gpc-cli/plugin-sdk, plugin hooks, plugin development, plugin scaffolding, gpc plugins init, beforeCommand, afterCommand, onError, beforeRequest, afterResponse, registerCommands, plugin permissions, plugin manifest, custom commands, plugin-ci, extend GPC, GPC addon — even if they don't explicitly say 'plugin.' Also trigger when someone wants to add custom behavior to GPC, integrate GPC with Slack or other services, build CI/CD extensions, hook into the command lifecycle, or register new CLI commands. For using the built-in CI plugin, see gpc-ci-integration.
SKILL.md
8.3 KB, as published. Nobody here has run it
gpc-plugin-development
Build and publish GPC plugins using the @gpc-cli/plugin-sdk.
When to use
- Building a new GPC plugin
- Adding custom hooks (notifications, logging, metrics)
- Registering custom CLI commands
- Understanding the plugin lifecycle and permission system
- Debugging plugin loading or hook execution
- Publishing a plugin to npm
Inputs required
- Node.js 20+ and TypeScript 5+
- @gpc-cli/plugin-sdk package (peer dependency)
- Plugin name —
@gpc-cli/plugin-*(first-party) orgpc-plugin-*(third-party)
Procedure
0. Scaffold a new plugin
# Generate plugin boilerplate
gpc plugins init my-notifier --description "Send Slack notifications on release"
# This creates:
# gpc-plugin-my-notifier/
# ├── package.json
# ├── tsconfig.json
# ├── src/index.ts
# └── tests/plugin.test.ts
Or manually:
mkdir gpc-plugin-my-notifier && cd gpc-plugin-my-notifier
npm init -y
npm install --save-peer @gpc-cli/plugin-sdk
npm install --save-dev typescript vitest
1. Implement the plugin interface
Every plugin exports a GpcPlugin object:
import type { GpcPlugin, PluginHooks } from "@gpc-cli/plugin-sdk";
export const plugin: GpcPlugin = {
name: "gpc-plugin-my-notifier",
version: "1.0.0",
register(hooks: PluginHooks) {
// Register your hooks here
hooks.afterCommand(async (event, result) => {
if (event.command === "releases upload" && result.success) {
console.log(`✓ Upload complete in ${result.durationMs}ms`);
}
});
},
};
export default plugin;
Read: references/hooks-reference.md for all 6 hook types with full type signatures.
2. Available lifecycle hooks
Register hooks inside the register() method:
register(hooks: PluginHooks) {
// Before any command runs
hooks.beforeCommand(async (event) => {
console.log(`Running: gpc ${event.command}`);
});
// After successful command
hooks.afterCommand(async (event, result) => {
console.log(`Done: ${result.durationMs}ms, exit ${result.exitCode}`);
});
// On command failure
hooks.onError(async (event, error) => {
console.error(`Failed: ${error.code} — ${error.message}`);
});
// Before each API request
hooks.beforeRequest(async (event) => {
console.log(`API: ${event.method} ${event.path}`);
});
// After each API response
hooks.afterResponse(async (event, response) => {
console.log(`API: ${response.status} in ${response.durationMs}ms`);
});
// Register custom CLI commands
hooks.registerCommands((registry) => {
registry.add({
name: "notify",
description: "Send a test notification",
action: async () => {
console.log("Notification sent!");
},
});
});
}
3. Declare permissions (third-party plugins)
Third-party plugins (gpc-plugin-*) must declare permissions:
{
"gpc": {
"permissions": [
"hooks:afterCommand",
"hooks:onError",
"api:read"
]
}
}
Read: references/permissions-system.md for the full permission list and trust model.
Available permissions:
| Permission | Allows |
|---|---|
read:config | Read .gpcrc.json |
write:config | Modify config |
read:auth | Access credentials |
api:read | Make read API calls |
api:write | Make write API calls |
commands:register | Register new commands |
hooks:beforeCommand | Hook before commands |
hooks:afterCommand | Hook after commands |
hooks:onError | Hook on errors |
hooks:beforeRequest | Hook before API requests |
hooks:afterResponse | Hook after API responses |
First-party plugins (@gpc-cli/*) are auto-trusted — no permissions needed.
Trust check order (v0.9.74+):
discoverPlugins()callsisPluginTrusted()before callingimport()on any plugin specifier. Untrusted plugins are silently skipped without their module code ever running. Previously, GPC imported first and checked approval afterward, which allowed top-level module side-effects to execute before the trust decision was made.
Permission enforcement (v0.9.80+): Permissions are now enforced at hook registration time, not just validated. A third-party plugin without
hooks:beforeRequestpermission that callshooks.beforeRequest()will see a warning instead of the hook being silently registered. Ifregister()throws, the error is caught and the plugin is skipped with a warning -- it cannot crash the CLI. Project.gpcrc.jsoncan no longer setapprovedPlugins-- only user config (~/.config/gpc/config.json) is trusted for plugin approval.
4. Test your plugin
// tests/plugin.test.ts
import { describe, it, expect, vi } from "vitest";
import { plugin } from "../src/index.js";
describe("my-notifier plugin", () => {
it("has required fields", () => {
expect(plugin.name).toBe("gpc-plugin-my-notifier");
expect(plugin.version).toBeDefined();
expect(typeof plugin.register).toBe("function");
});
it("registers afterCommand hook", () => {
const hooks = {
beforeCommand: vi.fn(),
afterCommand: vi.fn(),
onError: vi.fn(),
beforeRequest: vi.fn(),
afterResponse: vi.fn(),
registerCommands: vi.fn(),
};
plugin.register(hooks);
expect(hooks.afterCommand).toHaveBeenCalled();
});
});
npx vitest run
5. Install and configure
# Install locally
npm install ./gpc-plugin-my-notifier
# Or from npm
npm install -g @gpc-cli/cli-plugin-my-notifier
Add to .gpcrc.json:
{
"plugins": ["gpc-plugin-my-notifier"],
"approvedPlugins": ["gpc-plugin-my-notifier"]
}
Third-party plugins must be listed in approvedPlugins to load.
6. Publish to npm
# Build
npx tsc
# Test
npx vitest run
# Publish
npm publish
Naming convention:
- First-party:
@gpc-cli/plugin-<name>(reserved for official plugins) - Third-party:
gpc-plugin-<name>
Verification
gpc plugins listshows your plugin as loaded- Hooks fire at the expected lifecycle points
npx vitest runpasses all tests- Third-party permission errors show clear messages
- Plugin loads without blocking GPC startup
Failure modes / debugging
| Symptom | Likely Cause | Fix |
|---|---|---|
| Plugin not loading | Not in plugins config array | Add to .gpcrc.json plugins list |
PLUGIN_INVALID_PERMISSION | Unknown permission declared | Check valid permissions in references/permissions-system.md |
| Third-party plugin silently missing | Not in approvedPlugins | Add plugin name to approvedPlugins in config — unapproved plugins are skipped without error |
| Hook not firing | Wrong hook name or not registered | Verify hook registration in register() method |
| Hook error crashes GPC | Error in beforeCommand handler | onError and API hooks swallow errors; beforeCommand does not |
| Plugin not found | Wrong package name or not installed | Check node_modules for gpc-plugin-* or @gpc-cli/plugin-* |
| Standalone binary ignores plugins | Plugins disabled in binary mode | Use npm-installed GPC for plugin support |
gpc doctor warns on plugin | Plugin fails to load | Run gpc doctor to see which plugin failed, then reinstall it (v0.9.71+) |
Related skills
- gpc-ci-integration — uses @gpc-cli/plugin-ci as an example of a first-party plugin
- gpc-setup — configuration file where plugins are registered
- gpc-troubleshooting — debugging plugin loading issues