Help us improve
Share bugs, ideas, or general feedback.
From oagen
Scaffolds a language extractor for oagen's compat system that analyzes a live SDK and returns its public API surface as JSON to detect breaking changes between generated and live SDKs.
npx claudepluginhub workos/oagenHow this skill is triggered — by the user, by Claude, or both
Slash command
/oagen:generate-extractorThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Scaffold a language extractor for oagen's compat verification system. An extractor analyzes a live SDK and returns its public API surface as a canonical `ApiSurface` JSON, which the differ then compares against generated output to detect breaking changes.
Verifies generated SDK code preserves backwards compatibility with a live SDK by extracting the public API surface, generating code with a compatibility overlay, and diffing against the baseline to detect breaking changes, regressions, or API surface drift.
Extracts OpenAPI specs from existing API codebases in FastAPI, Flask, Django REST, Spring Boot, NestJS, Hono, Rails, and Laravel using framework-specific guides.
Generates type-safe client SDKs in TypeScript, Python, Go, Java from OpenAPI specs with auth, retries, pagination, and tests.
Share bugs, ideas, or general feedback.
Scaffold a language extractor for oagen's compat verification system. An extractor analyzes a live SDK and returns its public API surface as a canonical ApiSurface JSON, which the differ then compares against generated output to detect breaking changes.
Live SDK → Extractor → ApiSurface JSON → Differ ← Generated SDK → Violations
Each language needs its own extractor because public surface detection is language-specific (e.g., TypeScript exports vs. Ruby public methods vs. Python __all__ vs. Go capitalized identifiers). An extractor implements the Extractor interface and is exported through the emitter project's plugin bundle (e.g., workosEmittersPlugin), which the consumer project's oagen.config.ts imports.
Extractor interface, ApiSurface type, language-specific strategiesEmitter project: Use the project argument if provided, otherwise use AskUserQuestion.
oagen core: Check for node_modules/@workos/oagen/, or src/engine/types.ts in the current directory, otherwise ask.
Read and understand these files before starting:
@workos/oagen): Extractor, ApiSurface, ApiClass, ApiMethod, ApiParam, ApiProperty, ApiInterface, ApiField, ApiTypeAlias, ApiEnum{oagen}/src/compat/extractors/node.ts — study the structure, not the TypeScript-specific analysis{oagen}/test/compat/extractors/node.test.ts{oagen}/test/fixtures/sample-sdk/{oagen}/docs/architecture/extractor-contract.md — includes language-specific strategiesIf an sdk_path argument is provided, delegate SDK exploration to a subagent to keep file-reading noise out of the main context:
Use the Agent tool with subagent_type: Explore and a prompt like:
Explore the SDK at
{sdk_path}. Focus on: entry point discovery (main module/package), public surface detection (how public vs private symbols are distinguished), type information sources (compiler API, stubs, annotations), class/method extraction patterns, and file/directory layout. Return structured findings with real code snippets and file paths. Only report what you actually find.
The real SDK is the ground truth.
Before writing any code, determine how to analyze the target language's SDK:
| Decision | What it controls |
|---|---|
| Entry point discovery | How to find the SDK's main module/package |
| Public surface detection | How to distinguish public from private symbols |
| Type information source | Where types come from (compiler API, stubs, annotations) |
| Class/method extraction | How to extract classes, methods, params, return types |
| Analysis tooling | What npm packages or subprocess calls are needed |
Extractors run as TypeScript code. For non-TS/JS SDKs, choose one:
.rbi, .rbs, .pyi) or source files directlyPresent your proposed strategy to the user for confirmation, including any runtime dependencies.
Create src/compat/extractors/{language}.ts in the emitter project.
import type { ApiSurface, Extractor } from '@workos/oagen';
import { resolveHints } from '@workos/oagen';
export const {language}Extractor: Extractor = {
language: '{language}',
hints: resolveHints({
// Override only the hints that differ from Node/TypeScript defaults.
// See docs/architecture/extractor-contract.md for the full reference.
// stripNullable: (type) => ...,
// isExtractionArtifact: (type) => ...,
// tolerateCategoryMismatch: false,
// derivedModelNames: (name) => [...],
}),
async extract(sdkPath: string): Promise<ApiSurface> {
// 1. Discover entry point
// 2. Load and analyze public surface
// 3. Extract classes, interfaces, type aliases, enums
// 4. Build export map
// 5. Return sorted, deterministic ApiSurface
return {
language: '{language}',
extractedFrom: sdkPath,
extractedAt: new Date().toISOString(),
classes: sortRecord(classes),
interfaces: sortRecord(interfaces),
typeAliases: sortRecord(typeAliases),
enums: sortRecord(enums),
exports: sortRecord(exports),
};
},
};
classes, interfaces, typeAliases, enums, exports. Map as closely as possible even if the language doesn't have a direct equivalent for every category.hints — Every extractor must include a hints: LanguageHints field. Use resolveHints({...}) to start from Node defaults and override only the language-specific methods. Test that your hints produce correct results for the target language's type strings.Add the extractor to the plugin bundle export (e.g., src/plugin.ts) and re-export from src/index.ts:
// src/plugin.ts
import { {language}Extractor } from './compat/extractors/{language}.js';
export const workosEmittersPlugin = {
extractors: [/* existing, */ {language}Extractor],
// ...
};
// src/index.ts
export { {language}Extractor } from './compat/extractors/{language}.js';
The consumer project's oagen.config.ts imports the plugin bundle, so the new extractor is automatically available.
Create a minimal but representative fixture at test/fixtures/sample-sdk-{language}/ in the emitter project. It must include:
Organization with id, name)Mirror the Node fixture at {oagen}/test/fixtures/sample-sdk/ in terms of what it tests, using the target language's idioms.
Create test/compat/extractors/{language}.test.ts in the emitter project covering:
Use toMatchObject for partial assertions and toMatchInlineSnapshot() for at least one representative snapshot per category.
# All tests pass
cd {project} && npx vitest run
# Manual extraction against fixture
npx tsx -e "
import { {language}Extractor } from './src/compat/extractors/{language}.js';
const surface = await {language}Extractor.extract('test/fixtures/sample-sdk-{language}');
console.log(JSON.stringify(surface, null, 2));
" > /tmp/test-{language}-surface.json
# Determinism check — extract twice, diff
If sdk_path was provided, also test against the real SDK and verify all public classes are captured, method signatures match, and no private symbols leak through.
=== Extractor: {language} ===
Files created: src/compat/extractors/{language}.ts, test suite, fixture SDK
Modified: src/plugin.ts, src/index.ts
Validation: Tests / Fixture extract / Determinism / Real SDK extract
ApiSurface: {N} classes, {N} interfaces, {N} type aliases, {N} enums, {N} exports
This skill produces, in the emitter project:
src/compat/extractors/{language}.ts — extractor implementing the Extractor interface with LanguageHintstest/compat/extractors/{language}.test.ts — extraction tests against fixture SDKtest/fixtures/sample-sdk-{language}/ — minimal fixture SDK for testingsrc/plugin.ts) and src/index.ts with the new extractor registeredsdkPath parameter, never hardcode fixture pathsListResponse<Organization>)