write-plugin
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.
What this skill does
# Write aptx-ft Plugin
Create custom JS plugins that extend the aptx-ft CLI with new commands and code generation capabilities.
## When to Write a Plugin
| 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 |
## Command Name Mapping
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.
## Plugin File Structure
A plugin is a CommonJS or ESM module exporting a `Plugin` object:
```javascript
// 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;
```
## TypeScript Plugin Development
Plugins can be written in TypeScript. Since the CLI loads `.js` files at runtime, compile your `.ts` plugin first:
```bash
# 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.
### TypeScript Type Definitions
All types are exported from `@aptx/frontend-tk-core`. Install the package for type checking:
```bash
npm install -D @aptx/frontend-tk-core
```
#### Core Plugin Types
```typescript
// 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;
}
```
#### IR (Intermediate Representation) Types
`ctx.getIr(inputPath)` returns `GeneratorInput`. See [references/ir-types.md](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](references/http-params-guide.md) for a complete guide covering:
- Path parameters (URL path interpolation)
- Query parameters (URL query string)
- Request body (JSON payload)
- All combinations (path+query, path+body, query+body, path+query+body)
- Detection logic and code generation patterns
#### Type Relationships
```
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
```
## Common Patterns
### Pattern 1: Custom Code Generator
Generate non-standard output from OpenAPI endpoints:
```javascript
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}`);
}
},
```
### Pattern 2: Endpoint Analysis / Reporting
Read IR data and produce a report without generating files:
```javascript
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}`));
}
},
```
### Pattern 3: Multi-command Plugin
A plugin with several related commands:
```javascript
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 <typRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.