Write custom JS plugins for the aptx-ft CLI to add commands, generate code, or analyze OpenAPI specs. Use when: (1) writing or loading a plugin file (.js/.ts), (2) using --plugin/-p CLI flag, (3) creating custom CLI subcommands, (4) accessing parsed OpenAPI data via ctx.getIr/PluginContext/GeneratorInput, (5) building custom code generators (e.g. Axios clients), (6) producing reports from OpenAPI specs, (7) questions about PluginDescriptor/CommandDescriptor/OptionDescriptor. Do NOT use for standard generation (models, react-query, vue-query, barrel files) — use generate-artifacts or generate-models instead.
npx claudepluginhub haibaraaiaptx/frontend-openapi-skills --plugin frontend-openapi-skillsThis skill uses the workspace's default tool permissions.
Create custom JS plugins that extend the aptx-ft CLI with new commands and code generation capabilities.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Create custom JS plugins that extend the aptx-ft CLI with new commands and code generation capabilities.
| Scenario | Action |
|---|---|
| Need a custom code generator (e.g., Axios client, gRPC stub) | Write a plugin with command + custom rendering |
| Want to transform IR data into project-specific formats | Use ctx.getIr() to read OpenAPI IR |
| Need to add project-specific CLI commands to aptx-ft | Register commands via plugin |
| Built-in commands don't cover your use case | Extend with a plugin |
The plugin defines command names with a colon separator (e.g. my:generate), but the CLI splits this into two arguments at runtime:
Plugin name field | CLI invocation |
|---|---|
my:generate | aptx-ft my generate |
tk:lint | aptx-ft tk lint |
report:deps | aptx-ft report deps |
The first part becomes a namespace subcommand, the second part becomes the actual command.
A plugin is a CommonJS or ESM module exporting a Plugin object:
// my-plugin.js
const myPlugin = {
descriptor: {
name: 'my-plugin',
version: '1.0.0',
namespaceDescription: 'Custom code generation commands',
},
commands: [
{
name: 'my:generate',
summary: 'Generate custom output from OpenAPI',
options: [
{ flags: '-o, --output <dir>', description: 'Output directory', required: true },
{ flags: '--template <file>', description: 'Template file path' },
],
handler: async (ctx, args) => {
const inputPath = args.input; // global --input is available
const outputDir = args.output;
// Access parsed IR data
const ir = ctx.getIr(inputPath);
// Iterate endpoints
for (const ep of ir.endpoints) {
ctx.log(`Processing ${ep.method} ${ep.path} → ${ep.export_name}`);
// Your generation logic here
}
},
},
],
// Optional: runs once when plugin loads
init(ctx) {
ctx.log('my-plugin loaded');
},
};
module.exports = myPlugin;
module.exports.default = myPlugin;
Plugins can be written in TypeScript. Since the CLI loads .js files at runtime, compile your .ts plugin first:
# Compile the plugin
npx tsc my-plugin.ts --outDir ./dist --module commonjs --target ESNext
# Run the compiled plugin (colon in name becomes two CLI args)
pnpm exec aptx-ft -i ./openapi.json -p ./dist/my-plugin.js my generate -o ./output
The --plugin flag is global — place it before the subcommand. Each -p takes one path; repeat the flag for multiple plugins. The -i flag provides the OpenAPI file that ctx.getIr() reads.
All types are exported from @aptx/frontend-tk-core. Install the package for type checking:
npm install -D @aptx/frontend-tk-core
// Main plugin interface
interface Plugin {
descriptor: PluginDescriptor;
commands: CommandDescriptor[];
renderers?: RendererDescriptor[];
init?(context: PluginContext): void | Promise<void>;
}
// Plugin metadata
interface PluginDescriptor {
name: string;
version: string;
namespaceDescription?: string;
}
// Context passed to handlers and renderers
interface PluginContext {
binding: typeof import('@aptx/frontend-tk-binding');
log: (msg: string) => void;
getIr(inputPath: string): GeneratorInput;
}
// Command handler function type
type CommandHandler = (
ctx: PluginContext,
args: Record<string, unknown>,
) => Promise<void> | void;
// Command definition
interface CommandDescriptor {
name: string;
summary: string;
description?: string;
options: OptionDescriptor[];
examples?: string[];
handler: CommandHandler;
requiresOpenApi?: boolean; // default: true
}
// CLI option definition (Commander.js style)
interface OptionDescriptor {
flags: string; // e.g. "-o, --output <dir>"
description: string;
defaultValue?: string | boolean;
required?: boolean;
}
// Code renderer definition
interface RendererDescriptor {
id: string;
render: (
ctx: PluginContext,
options: Record<string, unknown>,
) => Promise<void> | void;
}
ctx.getIr(inputPath) returns GeneratorInput. See references/ir-types.md for full type definitions including GeneratorInput, EndpointItem, ProjectContext, ModelImportConfig, and ClientImportConfig.
Handling HTTP Request Parameters: Every endpoint may receive input through path parameters (path_fields), query parameters (query_fields), and request body (request_body_field). Your plugin must handle all three channels and their combinations. See references/http-params-guide.md for a complete guide covering:
Plugin
├── descriptor: PluginDescriptor
├── commands: CommandDescriptor[]
│ ├── options: OptionDescriptor[]
│ └── handler: CommandHandler(ctx: PluginContext, args)
├── renderers?: RendererDescriptor[]
└── init?(ctx: PluginContext)
PluginContext
├── binding: Rust native binding
├── log: (msg) => void
└── getIr(path) -> GeneratorInput
├── project: ProjectContext
├── endpoints: EndpointItem[]
├── model_import: ModelImportConfig | null
├── client_import: ClientImportConfig | null
└── output_root: string | null
Generate non-standard output from OpenAPI endpoints:
handler: async (ctx, args) => {
const ir = ctx.getIr(args.input);
const output = args.output;
const fs = await import('fs');
const path = await import('path');
// Filter endpoints by namespace
const endpoints = ir.endpoints.filter(
ep => ep.namespace.includes(args.namespace || '')
);
for (const ep of endpoints) {
const filename = `${ep.export_name}.ts`;
const content = generateCode(ep); // your logic
fs.writeFileSync(path.join(output, filename), content);
ctx.log(`Generated ${filename}`);
}
},
Read IR data and produce a report without generating files:
handler: async (ctx, args) => {
const ir = ctx.getIr(args.input);
ctx.log(`API: ${ir.project.package_name}`);
ctx.log(`Endpoints: ${ir.endpoints.length}`);
// Group by method
const byMethod = {};
for (const ep of ir.endpoints) {
(byMethod[ep.method] ??= []).push(ep);
}
for (const [method, eps] of Object.entries(byMethod)) {
ctx.log(` ${method.toUpperCase()}: ${eps.length}`);
}
// Find deprecated
const deprecated = ir.endpoints.filter(ep => ep.deprecated);
if (deprecated.length > 0) {
ctx.log(`\nDeprecated endpoints:`);
deprecated.forEach(ep => ctx.log(` - ${ep.method} ${ep.path}`));
}
},
A plugin with several related commands:
const plugin = {
descriptor: {
name: 'my-toolkit',
version: '1.0.0',
namespaceDescription: 'Custom development toolkit',
},
commands: [
{
name: 'tk:lint',
summary: 'Lint generated code',
options: [
{ flags: '--fix', description: 'Auto-fix issues', defaultValue: false },
],
handler: async (ctx, args) => { /* ... */ },
},
{
name: 'tk:stats',
summary: 'Show API statistics',
options: [],
handler: async (ctx, args) => { /* ... */ },
},
{
name: 'tk:convert',
summary: 'Convert output to another format',
options: [
{ flags: '--format <type>', description: 'Target format', required: true },
],
handler: async (ctx, args) => { /* ... */ },
},
],
};
namespace:command format (colon separator)my:generate → aptx-ft my generatectx.getIr() throws on invalid file path or malformed OpenAPI — handle errors in your handlermodule.exports and module.exports.default for compatibility.js or .mjs — compile .ts plugins first.node, .dll, .so, .dylib) are skipped[] if the command takes no flags@aptx/frontend-tk-core types for TypeScript plugins — install as dev dependencyrequiresOpenApi: false for commands that don't need OpenAPI input (default is true)args in handler is Record<string, unknown> — cast to specific types as neededctx.binding provides access to Rust native code via binding.runCli({...})HTTP Parameter Handling Rules:
path_fields, query_fields, request_body_fieldep.path template (replace {paramName} placeholders). Path params are always required?key=value&key2=value2. Handle optional params by omitting null/undefined valuesep.request_body_field (a single field name, not an array). Only present when the endpoint has a bodyep.path_fields.length > 0, ep.query_fields.length > 0, and !!ep.request_body_field to determine which channels are active--plugin and don't require rebuilding aptx-ft