@skill-tools/gen 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/skill-tools/skill-tools/packages/gen/dist/chunk-PZXBN36T.cjs","../src/openapi.ts","../src/renderer.ts","../src/generator.ts"],"names":[],"mappings":"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","file":"/home/runner/work/skill-tools/skill-tools/packages/gen/dist/chunk-PZXBN36T.cjs","sourcesContent":[null,"import YAML from 'yaml';\nimport type {\n\tApiEndpoint,\n\tApiParameter,\n\tApiProperty,\n\tApiRequestBody,\n\tApiResponse,\n\tApiSpec,\n\tAuthScheme,\n} from './types.js';\n\n/**\n * Parse an OpenAPI 3.x specification (JSON or YAML) into an ApiSpec.\n *\n * Supports OpenAPI 3.0.x and 3.1.x. Does not support Swagger 2.0.\n * Resolves local `$ref` references within the document.\n */\nexport function parseOpenApi(content: string): ApiSpec {\n\tconst MAX_SPEC_SIZE = 10_000_000; // 10MB\n\tif (content.length > MAX_SPEC_SIZE) {\n\t\tthrow new Error(`OpenAPI spec exceeds maximum size (${MAX_SPEC_SIZE} bytes)`);\n\t}\n\n\tconst doc = parseDocument(content);\n\n\tconst version = doc.openapi;\n\tif (!version || !version.startsWith('3.')) {\n\t\tthrow new Error(\n\t\t\t`Unsupported OpenAPI version: ${version ?? 'unknown'}. Only OpenAPI 3.x is supported.`,\n\t\t);\n\t}\n\n\tconst title = doc.info?.title ?? 'Untitled API';\n\tconst description = doc.info?.description ?? '';\n\tconst apiVersion = doc.info?.version ?? '0.0.0';\n\tconst servers = extractServers(doc);\n\tconst auth = extractAuthSchemes(doc);\n\tconst endpoints = extractEndpoints(doc);\n\n\treturn {\n\t\ttitle,\n\t\tdescription,\n\t\tversion: apiVersion,\n\t\tservers,\n\t\tauth,\n\t\tendpoints,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\ninterface OpenApiDoc {\n\topenapi?: string;\n\tinfo?: { title?: string; description?: string; version?: string };\n\tservers?: Array<{ url: string }>;\n\tpaths?: Record<string, Record<string, unknown>>;\n\tcomponents?: {\n\t\tsecuritySchemes?: Record<string, Record<string, unknown>>;\n\t\tschemas?: Record<string, Record<string, unknown>>;\n\t};\n\t[key: string]: unknown;\n}\n\n/**\n * Parse the document as JSON first, falling back to YAML.\n */\nfunction parseDocument(content: string): OpenApiDoc {\n\tconst trimmed = content.trim();\n\tif (trimmed.startsWith('{')) {\n\t\ttry {\n\t\t\treturn JSON.parse(trimmed) as OpenApiDoc;\n\t\t} catch {\n\t\t\t// Fall through to YAML\n\t\t}\n\t}\n\treturn YAML.parse(content) as OpenApiDoc;\n}\n\n/**\n * Resolve a local $ref like \"#/components/schemas/Pet\".\n */\nfunction resolveRef(doc: OpenApiDoc, ref: string): Record<string, unknown> {\n\tif (!ref.startsWith('#/')) {\n\t\treturn {};\n\t}\n\tconst path = ref.slice(2).split('/');\n\tlet current: unknown = doc;\n\tfor (const segment of path) {\n\t\tif (current == null || typeof current !== 'object') return {};\n\t\tcurrent = (current as Record<string, unknown>)[segment];\n\t}\n\treturn (current as Record<string, unknown>) ?? {};\n}\n\n/**\n * If obj has a $ref, resolve it. Otherwise return obj as-is.\n */\nfunction deref(doc: OpenApiDoc, obj: Record<string, unknown>): Record<string, unknown> {\n\tif (typeof obj.$ref === 'string') {\n\t\treturn resolveRef(doc, obj.$ref);\n\t}\n\treturn obj;\n}\n\nfunction extractServers(doc: OpenApiDoc): string[] {\n\tconst servers = doc.servers;\n\tif (!Array.isArray(servers)) return [];\n\treturn servers.map((s: Record<string, unknown>) => s.url as string).filter(Boolean);\n}\n\nfunction extractAuthSchemes(doc: OpenApiDoc): AuthScheme[] {\n\tconst components = doc.components as Record<string, unknown> | undefined;\n\tconst schemes = components?.securitySchemes as\n\t\t| Record<string, Record<string, unknown>>\n\t\t| undefined;\n\tif (!schemes) return [];\n\n\treturn Object.entries(schemes).map(([name, raw]) => {\n\t\tconst scheme = deref(doc, raw);\n\t\treturn {\n\t\t\ttype: (scheme.type as AuthScheme['type']) ?? 'apiKey',\n\t\t\tname,\n\t\t\tdescription: scheme.description as string | undefined,\n\t\t\tin: scheme.in as AuthScheme['in'],\n\t\t\tscheme: scheme.scheme as string | undefined,\n\t\t};\n\t});\n}\n\nfunction extractEndpoints(doc: OpenApiDoc): ApiEndpoint[] {\n\tconst paths = doc.paths as Record<string, Record<string, unknown>> | undefined;\n\tif (!paths) return [];\n\n\tconst endpoints: ApiEndpoint[] = [];\n\tconst httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];\n\n\tfor (const [path, pathItem] of Object.entries(paths)) {\n\t\tconst resolved = deref(doc, pathItem);\n\t\t// Path-level parameters\n\t\tconst pathParams = Array.isArray(resolved.parameters)\n\t\t\t? (resolved.parameters as Record<string, unknown>[]).map((p) => extractParameter(doc, p))\n\t\t\t: [];\n\n\t\tfor (const method of httpMethods) {\n\t\t\tconst operation = resolved[method] as Record<string, unknown> | undefined;\n\t\t\tif (!operation) continue;\n\n\t\t\tconst opParams = Array.isArray(operation.parameters)\n\t\t\t\t? (operation.parameters as Record<string, unknown>[]).map((p) => extractParameter(doc, p))\n\t\t\t\t: [];\n\n\t\t\t// Merge path-level and operation-level params (operation wins on conflict)\n\t\t\tconst paramMap = new Map<string, ApiParameter>();\n\t\t\tfor (const p of pathParams) paramMap.set(`${p.in}:${p.name}`, p);\n\t\t\tfor (const p of opParams) paramMap.set(`${p.in}:${p.name}`, p);\n\n\t\t\tconst requestBody = operation.requestBody\n\t\t\t\t? extractRequestBody(doc, deref(doc, operation.requestBody as Record<string, unknown>))\n\t\t\t\t: undefined;\n\n\t\t\tconst responses = extractResponses(\n\t\t\t\tdoc,\n\t\t\t\toperation.responses as Record<string, unknown> | undefined,\n\t\t\t);\n\n\t\t\tendpoints.push({\n\t\t\t\tmethod: method.toUpperCase(),\n\t\t\t\tpath,\n\t\t\t\toperationId: operation.operationId as string | undefined,\n\t\t\t\tsummary: operation.summary as string | undefined,\n\t\t\t\tdescription: operation.description as string | undefined,\n\t\t\t\ttags: Array.isArray(operation.tags) ? (operation.tags as string[]) : [],\n\t\t\t\tparameters: Array.from(paramMap.values()),\n\t\t\t\trequestBody,\n\t\t\t\tresponses,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn endpoints;\n}\n\nfunction extractParameter(doc: OpenApiDoc, raw: Record<string, unknown>): ApiParameter {\n\tconst param = deref(doc, raw);\n\tconst schema = param.schema ? deref(doc, param.schema as Record<string, unknown>) : {};\n\n\treturn {\n\t\tname: (param.name as string) ?? '',\n\t\tin: (param.in as ApiParameter['in']) ?? 'query',\n\t\tdescription: param.description as string | undefined,\n\t\trequired: (param.required as boolean) ?? false,\n\t\ttype: schema.type as string | undefined,\n\t\texample: param.example,\n\t};\n}\n\nfunction extractRequestBody(\n\tdoc: OpenApiDoc,\n\tbody: Record<string, unknown>,\n): ApiRequestBody | undefined {\n\tconst content = body.content as Record<string, Record<string, unknown>> | undefined;\n\tif (!content) return undefined;\n\n\t// Prefer application/json, fall back to first content type\n\tconst contentType = 'application/json' in content ? 'application/json' : Object.keys(content)[0];\n\tif (!contentType) return undefined;\n\n\tconst mediaType = content[contentType];\n\tif (!mediaType) return undefined;\n\n\tconst schema = mediaType.schema ? deref(doc, mediaType.schema as Record<string, unknown>) : {};\n\n\treturn {\n\t\tdescription: body.description as string | undefined,\n\t\tcontentType,\n\t\trequired: (body.required as boolean) ?? false,\n\t\tproperties: extractProperties(doc, schema),\n\t\texample: mediaType.example ?? schema.example,\n\t};\n}\n\nfunction extractProperties(doc: OpenApiDoc, schema: Record<string, unknown>): ApiProperty[] {\n\tconst properties = schema.properties as Record<string, Record<string, unknown>> | undefined;\n\tif (!properties) return [];\n\n\tconst requiredFields = new Set(\n\t\tArray.isArray(schema.required) ? (schema.required as string[]) : [],\n\t);\n\n\treturn Object.entries(properties).map(([name, raw]) => {\n\t\tconst prop = deref(doc, raw);\n\t\treturn {\n\t\t\tname,\n\t\t\ttype: (prop.type as string) ?? 'unknown',\n\t\t\tdescription: prop.description as string | undefined,\n\t\t\trequired: requiredFields.has(name),\n\t\t\texample: prop.example,\n\t\t};\n\t});\n}\n\nfunction extractResponses(\n\tdoc: OpenApiDoc,\n\tresponses: Record<string, unknown> | undefined,\n): ApiResponse[] {\n\tif (!responses) return [];\n\n\treturn Object.entries(responses).map(([statusCode, raw]) => {\n\t\tconst response = deref(doc, raw as Record<string, unknown>);\n\t\tconst content = response.content as Record<string, Record<string, unknown>> | undefined;\n\n\t\tlet contentType: string | undefined;\n\t\tlet properties: ApiProperty[] = [];\n\n\t\tif (content) {\n\t\t\tcontentType = 'application/json' in content ? 'application/json' : Object.keys(content)[0];\n\n\t\t\tif (contentType && content[contentType]) {\n\t\t\t\tconst mediaType = content[contentType]!;\n\t\t\t\tconst schema = mediaType.schema\n\t\t\t\t\t? deref(doc, mediaType.schema as Record<string, unknown>)\n\t\t\t\t\t: {};\n\t\t\t\tproperties = extractProperties(doc, schema);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tstatusCode,\n\t\t\tdescription: response.description as string | undefined,\n\t\t\tcontentType,\n\t\t\tproperties,\n\t\t};\n\t});\n}\n","import type {\n\tApiEndpoint,\n\tApiProperty,\n\tApiRequestBody,\n\tApiSpec,\n\tAuthScheme,\n\tGenerateOptions,\n} from './types.js';\n\n/**\n * Render a SKILL.md file from an ApiSpec.\n *\n * In \"unified\" mode, produces a single SKILL.md covering the entire API.\n * In \"per-endpoint\" mode, produces one SKILL.md per endpoint.\n */\nexport function renderSkillMd(spec: ApiSpec, options: GenerateOptions = {}): Map<string, string> {\n\tconst mode = options.mode ?? 'unified';\n\tconst files = new Map<string, string>();\n\n\tif (mode === 'unified') {\n\t\tconst name = options.name ?? toKebabCase(spec.title);\n\t\tconst content = renderUnified(spec, { ...options, name });\n\t\tfiles.set(`${name}/SKILL.md`, content);\n\t} else {\n\t\tfor (const endpoint of spec.endpoints) {\n\t\t\tconst epName = endpoint.operationId\n\t\t\t\t? toKebabCase(endpoint.operationId)\n\t\t\t\t: toKebabCase(`${endpoint.method}-${endpoint.path}`);\n\t\t\tconst content = renderSingleEndpoint(spec, endpoint, {\n\t\t\t\t...options,\n\t\t\t\tname: epName,\n\t\t\t});\n\t\t\tfiles.set(`${epName}/SKILL.md`, content);\n\t\t}\n\t}\n\n\treturn files;\n}\n\n// ---------------------------------------------------------------------------\n// Unified mode — one SKILL.md for the entire API\n// ---------------------------------------------------------------------------\n\nfunction renderUnified(spec: ApiSpec, options: GenerateOptions): string {\n\tconst lines: string[] = [];\n\n\t// Frontmatter\n\tlines.push('---');\n\tlines.push(`name: ${options.name}`);\n\tlines.push(`description: >-`, ` ${options.description ?? buildUnifiedDescription(spec)}`);\n\tlines.push('---');\n\tlines.push('');\n\n\t// Title\n\tlines.push(`# ${spec.title}`);\n\tlines.push('');\n\n\tif (spec.description) {\n\t\tlines.push(spec.description);\n\t\tlines.push('');\n\t}\n\n\t// Base URL\n\tif (spec.servers.length > 0) {\n\t\tlines.push('## Base URL');\n\t\tlines.push('');\n\t\tfor (const server of spec.servers) {\n\t\t\tlines.push(`- \\`${server}\\``);\n\t\t}\n\t\tlines.push('');\n\t}\n\n\t// Authentication\n\tif (spec.auth.length > 0) {\n\t\tlines.push('## Authentication');\n\t\tlines.push('');\n\t\tlines.push(renderAuth(spec.auth));\n\t\tlines.push('');\n\t}\n\n\t// Endpoints\n\tlines.push('## Endpoints');\n\tlines.push('');\n\n\t// Group by tag\n\tconst grouped = groupByTag(spec.endpoints);\n\tfor (const [tag, endpoints] of grouped) {\n\t\tif (tag !== '_untagged') {\n\t\t\tlines.push(`### ${tag}`);\n\t\t\tlines.push('');\n\t\t}\n\n\t\tfor (const ep of endpoints) {\n\t\t\tlines.push(renderEndpointSection(ep));\n\t\t\tlines.push('');\n\t\t}\n\t}\n\n\t// Error handling\n\tif (options.includeErrorHandling !== false) {\n\t\tlines.push('## Error Handling');\n\t\tlines.push('');\n\t\tlines.push(renderErrorHandling(spec));\n\t\tlines.push('');\n\t}\n\n\treturn lines.join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Per-endpoint mode — one SKILL.md per endpoint\n// ---------------------------------------------------------------------------\n\nfunction renderSingleEndpoint(\n\tspec: ApiSpec,\n\tendpoint: ApiEndpoint,\n\toptions: GenerateOptions,\n): string {\n\tconst lines: string[] = [];\n\n\tconst summary = endpoint.summary ?? endpoint.description ?? `${endpoint.method} ${endpoint.path}`;\n\n\t// Frontmatter\n\tlines.push('---');\n\tlines.push(`name: ${options.name}`);\n\tlines.push(\n\t\t`description: >-`,\n\t\t` ${options.description ?? `Use when the user wants to ${summary.toLowerCase()}. Calls ${endpoint.method} ${endpoint.path}.`}`,\n\t);\n\tlines.push('---');\n\tlines.push('');\n\n\t// Title\n\tlines.push(`# ${summary}`);\n\tlines.push('');\n\n\tif (endpoint.description && endpoint.description !== endpoint.summary) {\n\t\tlines.push(endpoint.description);\n\t\tlines.push('');\n\t}\n\n\t// Base URL\n\tif (spec.servers.length > 0) {\n\t\tlines.push(`**Base URL:** \\`${spec.servers[0]}\\``);\n\t\tlines.push('');\n\t}\n\n\t// Auth\n\tif (spec.auth.length > 0) {\n\t\tlines.push('## Authentication');\n\t\tlines.push('');\n\t\tlines.push(renderAuth(spec.auth));\n\t\tlines.push('');\n\t}\n\n\t// Endpoint details\n\tlines.push(`## ${endpoint.method} \\`${endpoint.path}\\``);\n\tlines.push('');\n\n\t// Parameters\n\tif (endpoint.parameters.length > 0) {\n\t\tlines.push('### Parameters');\n\t\tlines.push('');\n\t\tlines.push(renderParametersTable(endpoint));\n\t\tlines.push('');\n\t}\n\n\t// Request body\n\tif (endpoint.requestBody) {\n\t\tlines.push('### Request Body');\n\t\tlines.push('');\n\t\tlines.push(renderRequestBody(endpoint.requestBody, options));\n\t\tlines.push('');\n\t}\n\n\t// Responses\n\tif (endpoint.responses.length > 0) {\n\t\tlines.push('### Responses');\n\t\tlines.push('');\n\t\tlines.push(renderResponses(endpoint));\n\t\tlines.push('');\n\t}\n\n\t// Example\n\tif (options.includeExamples !== false) {\n\t\tlines.push('## Example');\n\t\tlines.push('');\n\t\tlines.push(renderExample(spec, endpoint));\n\t\tlines.push('');\n\t}\n\n\t// Error handling\n\tif (options.includeErrorHandling !== false) {\n\t\tlines.push('## Error Handling');\n\t\tlines.push('');\n\t\tlines.push(renderEndpointErrorHandling(endpoint));\n\t\tlines.push('');\n\t}\n\n\treturn lines.join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Shared rendering helpers\n// ---------------------------------------------------------------------------\n\nfunction buildUnifiedDescription(spec: ApiSpec): string {\n\tconst endpointCount = spec.endpoints.length;\n\tconst methods = new Set(spec.endpoints.map((e) => e.method));\n\tconst methodList = Array.from(methods).join(', ');\n\n\tif (spec.description) {\n\t\treturn `${spec.description.split('.')[0]}. Use when working with the ${spec.title} (${endpointCount} endpoints, ${methodList}).`;\n\t}\n\n\treturn `Interact with the ${spec.title}. Use when the user needs to call any of the ${endpointCount} available endpoints (${methodList}).`;\n}\n\nfunction renderAuth(auth: readonly AuthScheme[]): string {\n\tconst lines: string[] = [];\n\tfor (const scheme of auth) {\n\t\tswitch (scheme.type) {\n\t\t\tcase 'http':\n\t\t\t\tif (scheme.scheme === 'bearer') {\n\t\t\t\t\tlines.push('Use Bearer token authentication:');\n\t\t\t\t\tlines.push('```');\n\t\t\t\t\tlines.push('Authorization: Bearer <token>');\n\t\t\t\t\tlines.push('```');\n\t\t\t\t} else if (scheme.scheme === 'basic') {\n\t\t\t\t\tlines.push('Use HTTP Basic authentication:');\n\t\t\t\t\tlines.push('```');\n\t\t\t\t\tlines.push('Authorization: Basic <base64(username:password)>');\n\t\t\t\t\tlines.push('```');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'apiKey':\n\t\t\t\tlines.push(`Pass the API key via ${scheme.in ?? 'header'}:`);\n\t\t\t\tlines.push('```');\n\t\t\t\tlines.push(`${scheme.name}: <api-key>`);\n\t\t\t\tlines.push('```');\n\t\t\t\tbreak;\n\t\t\tcase 'oauth2':\n\t\t\t\tlines.push('Uses OAuth 2.0 authentication. Obtain an access token first.');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tif (scheme.description) {\n\t\t\t\t\tlines.push(scheme.description);\n\t\t\t\t}\n\t\t}\n\t}\n\treturn lines.join('\\n');\n}\n\nfunction groupByTag(endpoints: readonly ApiEndpoint[]): Map<string, ApiEndpoint[]> {\n\tconst grouped = new Map<string, ApiEndpoint[]>();\n\n\tfor (const ep of endpoints) {\n\t\tconst tag = ep.tags.length > 0 ? ep.tags[0]! : '_untagged';\n\t\tconst list = grouped.get(tag) ?? [];\n\t\tlist.push(ep);\n\t\tgrouped.set(tag, list);\n\t}\n\n\treturn grouped;\n}\n\nfunction renderEndpointSection(ep: ApiEndpoint): string {\n\tconst lines: string[] = [];\n\tconst heading = ep.summary ?? `${ep.method} ${ep.path}`;\n\n\tlines.push(`#### ${ep.method} \\`${ep.path}\\` — ${heading}`);\n\tlines.push('');\n\n\tif (ep.description && ep.description !== ep.summary) {\n\t\tlines.push(ep.description);\n\t\tlines.push('');\n\t}\n\n\t// Parameters table\n\tif (ep.parameters.length > 0) {\n\t\tlines.push(renderParametersTable(ep));\n\t\tlines.push('');\n\t}\n\n\t// Request body\n\tif (ep.requestBody) {\n\t\tlines.push(`**Request body** (\\`${ep.requestBody.contentType}\\`):`);\n\t\tlines.push('');\n\t\tlines.push(renderPropertiesTable(ep.requestBody.properties));\n\t\tlines.push('');\n\t}\n\n\t// Compact response summary\n\tconst successResponse = ep.responses.find((r) => r.statusCode.startsWith('2'));\n\tif (successResponse) {\n\t\tlines.push(\n\t\t\t`**Response:** ${successResponse.statusCode} — ${successResponse.description ?? 'Success'}`,\n\t\t);\n\t}\n\n\treturn lines.join('\\n');\n}\n\nfunction renderParametersTable(ep: ApiEndpoint): string {\n\tconst lines: string[] = [];\n\tlines.push('| Parameter | In | Type | Required | Description |');\n\tlines.push('|-----------|-----|------|----------|-------------|');\n\tfor (const param of ep.parameters) {\n\t\tconst req = param.required ? 'Yes' : 'No';\n\t\tconst desc = param.description ?? '';\n\t\tlines.push(`| \\`${param.name}\\` | ${param.in} | ${param.type ?? '-'} | ${req} | ${desc} |`);\n\t}\n\treturn lines.join('\\n');\n}\n\nfunction renderPropertiesTable(properties: readonly ApiProperty[]): string {\n\tif (properties.length === 0) return '';\n\n\tconst lines: string[] = [];\n\tlines.push('| Property | Type | Required | Description |');\n\tlines.push('|----------|------|----------|-------------|');\n\tfor (const prop of properties) {\n\t\tconst req = prop.required ? 'Yes' : 'No';\n\t\tconst desc = prop.description ?? '';\n\t\tlines.push(`| \\`${prop.name}\\` | ${prop.type} | ${req} | ${desc} |`);\n\t}\n\treturn lines.join('\\n');\n}\n\nfunction renderRequestBody(body: ApiRequestBody, options: GenerateOptions): string {\n\tconst lines: string[] = [];\n\tif (body.description) {\n\t\tlines.push(body.description);\n\t\tlines.push('');\n\t}\n\tlines.push(`Content-Type: \\`${body.contentType}\\``);\n\tlines.push('');\n\n\tif (body.properties.length > 0) {\n\t\tlines.push(renderPropertiesTable(body.properties));\n\t}\n\n\tif (options.includeExamples !== false && body.example) {\n\t\tlines.push('');\n\t\tlines.push('**Example:**');\n\t\tlines.push('```json');\n\t\tlines.push(JSON.stringify(body.example, null, 2));\n\t\tlines.push('```');\n\t}\n\n\treturn lines.join('\\n');\n}\n\nfunction renderResponses(ep: ApiEndpoint): string {\n\tconst lines: string[] = [];\n\tfor (const resp of ep.responses) {\n\t\tlines.push(`**${resp.statusCode}** — ${resp.description ?? ''}`);\n\t\tif (resp.properties.length > 0) {\n\t\t\tlines.push('');\n\t\t\tlines.push(renderPropertiesTable(resp.properties));\n\t\t}\n\t\tlines.push('');\n\t}\n\treturn lines.join('\\n');\n}\n\nfunction renderExample(spec: ApiSpec, ep: ApiEndpoint): string {\n\tconst baseUrl = spec.servers[0] ?? 'https://api.example.com';\n\tconst lines: string[] = [];\n\n\tlines.push('```bash');\n\tlines.push(`curl -X ${ep.method} \"${baseUrl}${ep.path}\" \\\\`);\n\n\t// Add auth header hint\n\tif (spec.auth.length > 0) {\n\t\tconst authScheme = spec.auth[0]!;\n\t\tif (authScheme.type === 'http' && authScheme.scheme === 'bearer') {\n\t\t\tlines.push(' -H \"Authorization: Bearer $TOKEN\" \\\\');\n\t\t} else if (authScheme.type === 'apiKey') {\n\t\t\tlines.push(` -H \"${authScheme.name}: $API_KEY\" \\\\`);\n\t\t}\n\t}\n\n\tlines.push(' -H \"Content-Type: application/json\"');\n\n\tif (ep.requestBody?.example) {\n\t\tlines.push(` -d '${JSON.stringify(ep.requestBody.example)}'`);\n\t}\n\n\tlines.push('```');\n\treturn lines.join('\\n');\n}\n\nfunction renderErrorHandling(spec: ApiSpec): string {\n\tconst errorCodes = new Set<string>();\n\tfor (const ep of spec.endpoints) {\n\t\tfor (const resp of ep.responses) {\n\t\t\tif (resp.statusCode.startsWith('4') || resp.statusCode.startsWith('5')) {\n\t\t\t\terrorCodes.add(resp.statusCode);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst lines: string[] = [];\n\tlines.push('Common error responses:');\n\tlines.push('');\n\n\tif (errorCodes.size === 0) {\n\t\tlines.push('- **4xx**: Client error — check request parameters and authentication');\n\t\tlines.push('- **5xx**: Server error — retry with exponential backoff');\n\t} else {\n\t\tfor (const code of Array.from(errorCodes).sort()) {\n\t\t\tlines.push(`- **${code}**: ${httpStatusDescription(code)}`);\n\t\t}\n\t}\n\n\tlines.push('');\n\tlines.push('When an error occurs:');\n\tlines.push('1. Check the response body for a detailed error message');\n\tlines.push('2. Verify authentication credentials are valid');\n\tlines.push('3. For 429 (rate limit), wait and retry with exponential backoff');\n\tlines.push('4. For 5xx errors, retry up to 3 times with backoff');\n\n\treturn lines.join('\\n');\n}\n\nfunction renderEndpointErrorHandling(ep: ApiEndpoint): string {\n\tconst errorResponses = ep.responses.filter(\n\t\t(r) => r.statusCode.startsWith('4') || r.statusCode.startsWith('5'),\n\t);\n\n\tconst lines: string[] = [];\n\n\tif (errorResponses.length > 0) {\n\t\tlines.push('Possible errors:');\n\t\tlines.push('');\n\t\tfor (const resp of errorResponses) {\n\t\t\tlines.push(\n\t\t\t\t`- **${resp.statusCode}**: ${resp.description ?? httpStatusDescription(resp.statusCode)}`,\n\t\t\t);\n\t\t}\n\t} else {\n\t\tlines.push('- Check authentication credentials if you receive a 401/403');\n\t\tlines.push('- Validate request parameters for 400 errors');\n\t\tlines.push('- Retry on 5xx with exponential backoff');\n\t}\n\n\treturn lines.join('\\n');\n}\n\nfunction httpStatusDescription(code: string): string {\n\tconst descriptions: Record<string, string> = {\n\t\t'400': 'Bad Request — check request parameters',\n\t\t'401': 'Unauthorized — check authentication',\n\t\t'403': 'Forbidden — insufficient permissions',\n\t\t'404': 'Not Found — resource does not exist',\n\t\t'405': 'Method Not Allowed',\n\t\t'409': 'Conflict — resource state conflict',\n\t\t'422': 'Unprocessable Entity — validation error',\n\t\t'429': 'Too Many Requests — rate limited, retry after backoff',\n\t\t'500': 'Internal Server Error — retry with backoff',\n\t\t'502': 'Bad Gateway — upstream service error',\n\t\t'503': 'Service Unavailable — retry later',\n\t};\n\treturn descriptions[code] ?? `HTTP ${code} error`;\n}\n\n/**\n * Convert a string to kebab-case.\n */\nfunction toKebabCase(input: string): string {\n\treturn input\n\t\t.replace(/[^a-zA-Z0-9\\s-]/g, '')\n\t\t.replace(/\\s+/g, '-')\n\t\t.replace(/([a-z])([A-Z])/g, '$1-$2')\n\t\t.replace(/-+/g, '-')\n\t\t.toLowerCase()\n\t\t.replace(/^-|-$/g, '')\n\t\t.slice(0, 64);\n}\n","import { readFile } from 'node:fs/promises';\nimport { countTokens } from '@skill-tools/core';\nimport { parseOpenApi } from './openapi.js';\nimport { renderSkillMd } from './renderer.js';\nimport type { ApiSpec, GenerateError, GenerateOptions, GenerateResult } from './types.js';\n\n/**\n * Generate SKILL.md files from an OpenAPI specification file.\n *\n * @param specPath - Path to an OpenAPI 3.x spec (JSON or YAML)\n * @param options - Generation options\n * @returns Generation result with files map, or an error\n */\nexport async function generateFromOpenApi(\n\tspecPath: string,\n\toptions: GenerateOptions = {},\n): Promise<GenerateResult | GenerateError> {\n\ttry {\n\t\tconst content = await readFile(specPath, 'utf-8');\n\t\tconst spec = parseOpenApi(content);\n\t\treturn generateFromSpec(spec, options);\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t};\n\t}\n}\n\n/**\n * Generate SKILL.md files from a pre-parsed ApiSpec.\n *\n * @param spec - The API specification\n * @param options - Generation options\n * @returns Generation result with files map\n */\nexport function generateFromSpec(\n\tspec: ApiSpec,\n\toptions: GenerateOptions = {},\n): GenerateResult | GenerateError {\n\ttry {\n\t\tconst files = renderSkillMd(spec, options);\n\n\t\t// Compute total token count\n\t\tlet totalTokens = 0;\n\t\tfor (const content of files.values()) {\n\t\t\ttotalTokens += countTokens(content);\n\t\t}\n\n\t\t// If maxTokens is set and exceeded in unified mode, warn but don't fail\n\t\tconst maxTokens = options.maxTokens ?? 4000;\n\t\tif (options.mode !== 'per-endpoint' && totalTokens > maxTokens) {\n\t\t\t// Try to truncate — for now, we just return as-is with the count.\n\t\t\t// Future: implement smart truncation.\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tfiles,\n\t\t\tendpointCount: spec.endpoints.length,\n\t\t\ttokenCount: totalTokens,\n\t\t};\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t};\n\t}\n}\n\n/**\n * Generate a SKILL.md from a simple text description.\n *\n * This is a lightweight alternative for when you don't have an OpenAPI spec.\n * Produces a basic SKILL.md with the given name and description.\n */\nexport function generateFromText(\n\tname: string,\n\tdescription: string,\n\tinstructions: string = '',\n): GenerateResult {\n\tconst kebabName = name\n\t\t.replace(/[^a-zA-Z0-9\\s-]/g, '')\n\t\t.replace(/\\s+/g, '-')\n\t\t.toLowerCase()\n\t\t.slice(0, 64);\n\n\tconst lines: string[] = [];\n\tlines.push('---');\n\tlines.push(`name: ${kebabName}`);\n\tlines.push(`description: >-`);\n\tlines.push(` ${description}`);\n\tlines.push('---');\n\tlines.push('');\n\tlines.push(`# ${name}`);\n\tlines.push('');\n\tlines.push(description);\n\tlines.push('');\n\n\tif (instructions) {\n\t\tlines.push('## Instructions');\n\t\tlines.push('');\n\t\tlines.push(instructions);\n\t\tlines.push('');\n\t}\n\n\tconst content = lines.join('\\n');\n\tconst files = new Map<string, string>();\n\tfiles.set(`${kebabName}/SKILL.md`, content);\n\n\treturn {\n\t\tok: true,\n\t\tfiles,\n\t\tendpointCount: 0,\n\t\ttokenCount: countTokens(content),\n\t};\n}\n"]}
package/dist/cli.cjs ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ "use strict"; function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
3
+
4
+
5
+ var _chunkPZXBN36Tcjs = require('./chunk-PZXBN36T.cjs');
6
+
7
+ // src/cli.ts
8
+ var _promises = require('fs/promises');
9
+ var _path = require('path');
10
+ var _commander = require('commander');
11
+ var program = new (0, _commander.Command)();
12
+ program.name("skillgen").description("Generate Agent Skills (SKILL.md) from API specifications").version("0.1.0");
13
+ program.command("openapi").description("Generate SKILL.md from an OpenAPI 3.x specification").argument("<spec>", "Path to OpenAPI spec file (JSON or YAML)").option("-o, --out <dir>", "Output directory", ".").option("-n, --name <name>", "Skill name (kebab-case)").option("-m, --mode <mode>", "Generation mode: unified or per-endpoint", "unified").option("--max-tokens <n>", "Maximum token budget", "4000").option("--no-examples", "Exclude example requests/responses").option("--no-error-handling", "Exclude error handling section").option("-d, --description <desc>", "Custom description override").action(async (specPath, opts) => {
14
+ const maxTokens = Number(opts.maxTokens);
15
+ if (Number.isNaN(maxTokens) || maxTokens < 0) {
16
+ console.error("Error: --max-tokens must be a positive number");
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ const options = {
21
+ name: opts.name,
22
+ outDir: opts.out,
23
+ mode: opts.mode,
24
+ maxTokens,
25
+ includeExamples: opts.examples !== false,
26
+ includeErrorHandling: opts.errorHandling !== false,
27
+ description: opts.description
28
+ };
29
+ const result = await _chunkPZXBN36Tcjs.generateFromOpenApi.call(void 0, _path.resolve.call(void 0, specPath), options);
30
+ if (!result.ok) {
31
+ console.error(`Error: ${result.error}`);
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+ const outDir = _path.resolve.call(void 0, _nullishCoalesce(options.outDir, () => ( ".")));
36
+ let written = 0;
37
+ for (const [filePath, content] of result.files) {
38
+ const fullPath = safeResolvePath(outDir, filePath);
39
+ await _promises.mkdir.call(void 0, _path.dirname.call(void 0, fullPath), { recursive: true });
40
+ await _promises.writeFile.call(void 0, fullPath, content, "utf-8");
41
+ console.log(` Created: ${fullPath}`);
42
+ written++;
43
+ }
44
+ console.log("");
45
+ console.log(`Generated ${written} skill file(s) from ${result.endpointCount} endpoints`);
46
+ console.log(`Total tokens: ${result.tokenCount}`);
47
+ if (result.tokenCount > maxTokens) {
48
+ console.log("");
49
+ console.log(
50
+ `Warning: Generated content exceeds token budget (${result.tokenCount} > ${maxTokens}).`
51
+ );
52
+ console.log("Consider using --mode per-endpoint to split into separate skills.");
53
+ }
54
+ });
55
+ program.command("from-text").description("Generate a basic SKILL.md from a name and description").argument("<name>", "Skill name").argument("<description>", "Skill description").option("-o, --out <dir>", "Output directory", ".").option("-i, --instructions <text>", "Additional instructions to include").action(async (name, description, opts) => {
56
+ const result = _chunkPZXBN36Tcjs.generateFromText.call(void 0, name, description, opts.instructions);
57
+ const outDir = _path.resolve.call(void 0, _nullishCoalesce(opts.out, () => ( ".")));
58
+ for (const [filePath, content] of result.files) {
59
+ const fullPath = safeResolvePath(outDir, filePath);
60
+ await _promises.mkdir.call(void 0, _path.dirname.call(void 0, fullPath), { recursive: true });
61
+ await _promises.writeFile.call(void 0, fullPath, content, "utf-8");
62
+ console.log(` Created: ${fullPath}`);
63
+ }
64
+ console.log("");
65
+ console.log(`Generated skill "${name}" (${result.tokenCount} tokens)`);
66
+ });
67
+ function safeResolvePath(baseDir, filePath) {
68
+ const resolved = _path.resolve.call(void 0, baseDir, filePath);
69
+ const normalizedBase = _path.resolve.call(void 0, baseDir);
70
+ if (!resolved.startsWith(`${normalizedBase}/`) && resolved !== normalizedBase) {
71
+ throw new Error(`Path traversal detected: "${filePath}" escapes output directory`);
72
+ }
73
+ return resolved;
74
+ }
75
+ program.parse();
76
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/skill-tools/skill-tools/packages/gen/dist/cli.cjs","../src/cli.ts"],"names":[],"mappings":"AAAA;AACA;AACE;AACA;AACF,wDAA6B;AAC7B;AACA;ACLA,uCAAiC;AACjC,4BAAiC;AACjC,sCAAwB;AAIxB,IAAM,QAAA,EAAU,IAAI,uBAAA,CAAQ,CAAA;AAE5B,OAAA,CACE,IAAA,CAAK,UAAU,CAAA,CACf,WAAA,CAAY,0DAA0D,CAAA,CACtE,OAAA,CAAQ,OAAO,CAAA;AAEjB,OAAA,CACE,OAAA,CAAQ,SAAS,CAAA,CACjB,WAAA,CAAY,qDAAqD,CAAA,CACjE,QAAA,CAAS,QAAA,EAAU,0CAA0C,CAAA,CAC7D,MAAA,CAAO,iBAAA,EAAmB,kBAAA,EAAoB,GAAG,CAAA,CACjD,MAAA,CAAO,mBAAA,EAAqB,yBAAyB,CAAA,CACrD,MAAA,CAAO,mBAAA,EAAqB,0CAAA,EAA4C,SAAS,CAAA,CACjF,MAAA,CAAO,kBAAA,EAAoB,sBAAA,EAAwB,MAAM,CAAA,CACzD,MAAA,CAAO,eAAA,EAAiB,oCAAoC,CAAA,CAC5D,MAAA,CAAO,qBAAA,EAAuB,gCAAgC,CAAA,CAC9D,MAAA,CAAO,0BAAA,EAA4B,6BAA6B,CAAA,CAChE,MAAA,CAAO,MAAA,CAAO,QAAA,EAAkB,IAAA,EAAA,GAAkC;AAClE,EAAA,MAAM,UAAA,EAAY,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA;AACvC,EAAA,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,SAAS,EAAA,GAAK,UAAA,EAAY,CAAA,EAAG;AAC7C,IAAA,OAAA,CAAQ,KAAA,CAAM,+CAA+C,CAAA;AAC7D,IAAA,OAAA,CAAQ,SAAA,EAAW,CAAA;AACnB,IAAA,MAAA;AAAA,EACD;AAEA,EAAA,MAAM,QAAA,EAA2B;AAAA,IAChC,IAAA,EAAM,IAAA,CAAK,IAAA;AAAA,IACX,MAAA,EAAQ,IAAA,CAAK,GAAA;AAAA,IACb,IAAA,EAAM,IAAA,CAAK,IAAA;AAAA,IACX,SAAA;AAAA,IACA,eAAA,EAAiB,IAAA,CAAK,SAAA,IAAa,KAAA;AAAA,IACnC,oBAAA,EAAsB,IAAA,CAAK,cAAA,IAAkB,KAAA;AAAA,IAC7C,WAAA,EAAa,IAAA,CAAK;AAAA,EACnB,CAAA;AAEA,EAAA,MAAM,OAAA,EAAS,MAAM,mDAAA,2BAAoB,QAAgB,CAAA,EAAG,OAAO,CAAA;AAEnE,EAAA,GAAA,CAAI,CAAC,MAAA,CAAO,EAAA,EAAI;AACf,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,OAAA,EAAU,MAAA,CAAO,KAAK,CAAA,CAAA;AACjB,IAAA;AACnB,IAAA;AACD,EAAA;AAE+B,EAAA;AACjB,EAAA;AAEoB,EAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACC,IAAA;AACpC,IAAA;AACD,EAAA;AAEc,EAAA;AACkB,EAAA;AACI,EAAA;AAED,EAAA;AACpB,IAAA;AACN,IAAA;AACP,MAAA;AACD,IAAA;AACY,IAAA;AACb,EAAA;AACA;AAIY;AAM0B,EAAA;AAEY,EAAA;AAEhB,EAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACC,IAAA;AACrC,EAAA;AAEc,EAAA;AACsB,EAAA;AACpC;AAMwC;AACP,EAAA;AACI,EAAA;AACV,EAAA;AACX,IAAA;AACjB,EAAA;AACO,EAAA;AACR;AAEc","file":"/home/runner/work/skill-tools/skill-tools/packages/gen/dist/cli.cjs","sourcesContent":[null,"#!/usr/bin/env node\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { generateFromOpenApi, generateFromText } from './generator.js';\nimport type { GenerateOptions } from './types.js';\n\nconst program = new Command();\n\nprogram\n\t.name('skillgen')\n\t.description('Generate Agent Skills (SKILL.md) from API specifications')\n\t.version('0.1.0');\n\nprogram\n\t.command('openapi')\n\t.description('Generate SKILL.md from an OpenAPI 3.x specification')\n\t.argument('<spec>', 'Path to OpenAPI spec file (JSON or YAML)')\n\t.option('-o, --out <dir>', 'Output directory', '.')\n\t.option('-n, --name <name>', 'Skill name (kebab-case)')\n\t.option('-m, --mode <mode>', 'Generation mode: unified or per-endpoint', 'unified')\n\t.option('--max-tokens <n>', 'Maximum token budget', '4000')\n\t.option('--no-examples', 'Exclude example requests/responses')\n\t.option('--no-error-handling', 'Exclude error handling section')\n\t.option('-d, --description <desc>', 'Custom description override')\n\t.action(async (specPath: string, opts: Record<string, unknown>) => {\n\t\tconst maxTokens = Number(opts.maxTokens);\n\t\tif (Number.isNaN(maxTokens) || maxTokens < 0) {\n\t\t\tconsole.error('Error: --max-tokens must be a positive number');\n\t\t\tprocess.exitCode = 1;\n\t\t\treturn;\n\t\t}\n\n\t\tconst options: GenerateOptions = {\n\t\t\tname: opts.name as string | undefined,\n\t\t\toutDir: opts.out as string,\n\t\t\tmode: opts.mode as 'unified' | 'per-endpoint',\n\t\t\tmaxTokens,\n\t\t\tincludeExamples: opts.examples !== false,\n\t\t\tincludeErrorHandling: opts.errorHandling !== false,\n\t\t\tdescription: opts.description as string | undefined,\n\t\t};\n\n\t\tconst result = await generateFromOpenApi(resolve(specPath), options);\n\n\t\tif (!result.ok) {\n\t\t\tconsole.error(`Error: ${result.error}`);\n\t\t\tprocess.exitCode = 1;\n\t\t\treturn;\n\t\t}\n\n\t\tconst outDir = resolve(options.outDir ?? '.');\n\t\tlet written = 0;\n\n\t\tfor (const [filePath, content] of result.files) {\n\t\t\tconst fullPath = safeResolvePath(outDir, filePath);\n\t\t\tawait mkdir(dirname(fullPath), { recursive: true });\n\t\t\tawait writeFile(fullPath, content, 'utf-8');\n\t\t\tconsole.log(` Created: ${fullPath}`);\n\t\t\twritten++;\n\t\t}\n\n\t\tconsole.log('');\n\t\tconsole.log(`Generated ${written} skill file(s) from ${result.endpointCount} endpoints`);\n\t\tconsole.log(`Total tokens: ${result.tokenCount}`);\n\n\t\tif (result.tokenCount > maxTokens) {\n\t\t\tconsole.log('');\n\t\t\tconsole.log(\n\t\t\t\t`Warning: Generated content exceeds token budget (${result.tokenCount} > ${maxTokens}).`,\n\t\t\t);\n\t\t\tconsole.log('Consider using --mode per-endpoint to split into separate skills.');\n\t\t}\n\t});\n\nprogram\n\t.command('from-text')\n\t.description('Generate a basic SKILL.md from a name and description')\n\t.argument('<name>', 'Skill name')\n\t.argument('<description>', 'Skill description')\n\t.option('-o, --out <dir>', 'Output directory', '.')\n\t.option('-i, --instructions <text>', 'Additional instructions to include')\n\t.action(async (name: string, description: string, opts: Record<string, unknown>) => {\n\t\tconst result = generateFromText(name, description, opts.instructions as string | undefined);\n\n\t\tconst outDir = resolve((opts.out as string) ?? '.');\n\n\t\tfor (const [filePath, content] of result.files) {\n\t\t\tconst fullPath = safeResolvePath(outDir, filePath);\n\t\t\tawait mkdir(dirname(fullPath), { recursive: true });\n\t\t\tawait writeFile(fullPath, content, 'utf-8');\n\t\t\tconsole.log(` Created: ${fullPath}`);\n\t\t}\n\n\t\tconsole.log('');\n\t\tconsole.log(`Generated skill \"${name}\" (${result.tokenCount} tokens)`);\n\t});\n\n/**\n * Resolve a file path within a base directory, preventing path traversal.\n * Throws if the resolved path escapes the base directory.\n */\nfunction safeResolvePath(baseDir: string, filePath: string): string {\n\tconst resolved = resolve(baseDir, filePath);\n\tconst normalizedBase = resolve(baseDir);\n\tif (!resolved.startsWith(`${normalizedBase}/`) && resolved !== normalizedBase) {\n\t\tthrow new Error(`Path traversal detected: \"${filePath}\" escapes output directory`);\n\t}\n\treturn resolved;\n}\n\nprogram.parse();\n"]}
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generateFromOpenApi,
4
+ generateFromText
5
+ } from "./chunk-EMDMG3H6.js";
6
+
7
+ // src/cli.ts
8
+ import { mkdir, writeFile } from "fs/promises";
9
+ import { dirname, resolve } from "path";
10
+ import { Command } from "commander";
11
+ var program = new Command();
12
+ program.name("skillgen").description("Generate Agent Skills (SKILL.md) from API specifications").version("0.1.0");
13
+ program.command("openapi").description("Generate SKILL.md from an OpenAPI 3.x specification").argument("<spec>", "Path to OpenAPI spec file (JSON or YAML)").option("-o, --out <dir>", "Output directory", ".").option("-n, --name <name>", "Skill name (kebab-case)").option("-m, --mode <mode>", "Generation mode: unified or per-endpoint", "unified").option("--max-tokens <n>", "Maximum token budget", "4000").option("--no-examples", "Exclude example requests/responses").option("--no-error-handling", "Exclude error handling section").option("-d, --description <desc>", "Custom description override").action(async (specPath, opts) => {
14
+ const maxTokens = Number(opts.maxTokens);
15
+ if (Number.isNaN(maxTokens) || maxTokens < 0) {
16
+ console.error("Error: --max-tokens must be a positive number");
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ const options = {
21
+ name: opts.name,
22
+ outDir: opts.out,
23
+ mode: opts.mode,
24
+ maxTokens,
25
+ includeExamples: opts.examples !== false,
26
+ includeErrorHandling: opts.errorHandling !== false,
27
+ description: opts.description
28
+ };
29
+ const result = await generateFromOpenApi(resolve(specPath), options);
30
+ if (!result.ok) {
31
+ console.error(`Error: ${result.error}`);
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+ const outDir = resolve(options.outDir ?? ".");
36
+ let written = 0;
37
+ for (const [filePath, content] of result.files) {
38
+ const fullPath = safeResolvePath(outDir, filePath);
39
+ await mkdir(dirname(fullPath), { recursive: true });
40
+ await writeFile(fullPath, content, "utf-8");
41
+ console.log(` Created: ${fullPath}`);
42
+ written++;
43
+ }
44
+ console.log("");
45
+ console.log(`Generated ${written} skill file(s) from ${result.endpointCount} endpoints`);
46
+ console.log(`Total tokens: ${result.tokenCount}`);
47
+ if (result.tokenCount > maxTokens) {
48
+ console.log("");
49
+ console.log(
50
+ `Warning: Generated content exceeds token budget (${result.tokenCount} > ${maxTokens}).`
51
+ );
52
+ console.log("Consider using --mode per-endpoint to split into separate skills.");
53
+ }
54
+ });
55
+ program.command("from-text").description("Generate a basic SKILL.md from a name and description").argument("<name>", "Skill name").argument("<description>", "Skill description").option("-o, --out <dir>", "Output directory", ".").option("-i, --instructions <text>", "Additional instructions to include").action(async (name, description, opts) => {
56
+ const result = generateFromText(name, description, opts.instructions);
57
+ const outDir = resolve(opts.out ?? ".");
58
+ for (const [filePath, content] of result.files) {
59
+ const fullPath = safeResolvePath(outDir, filePath);
60
+ await mkdir(dirname(fullPath), { recursive: true });
61
+ await writeFile(fullPath, content, "utf-8");
62
+ console.log(` Created: ${fullPath}`);
63
+ }
64
+ console.log("");
65
+ console.log(`Generated skill "${name}" (${result.tokenCount} tokens)`);
66
+ });
67
+ function safeResolvePath(baseDir, filePath) {
68
+ const resolved = resolve(baseDir, filePath);
69
+ const normalizedBase = resolve(baseDir);
70
+ if (!resolved.startsWith(`${normalizedBase}/`) && resolved !== normalizedBase) {
71
+ throw new Error(`Path traversal detected: "${filePath}" escapes output directory`);
72
+ }
73
+ return resolved;
74
+ }
75
+ program.parse();
76
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { generateFromOpenApi, generateFromText } from './generator.js';\nimport type { GenerateOptions } from './types.js';\n\nconst program = new Command();\n\nprogram\n\t.name('skillgen')\n\t.description('Generate Agent Skills (SKILL.md) from API specifications')\n\t.version('0.1.0');\n\nprogram\n\t.command('openapi')\n\t.description('Generate SKILL.md from an OpenAPI 3.x specification')\n\t.argument('<spec>', 'Path to OpenAPI spec file (JSON or YAML)')\n\t.option('-o, --out <dir>', 'Output directory', '.')\n\t.option('-n, --name <name>', 'Skill name (kebab-case)')\n\t.option('-m, --mode <mode>', 'Generation mode: unified or per-endpoint', 'unified')\n\t.option('--max-tokens <n>', 'Maximum token budget', '4000')\n\t.option('--no-examples', 'Exclude example requests/responses')\n\t.option('--no-error-handling', 'Exclude error handling section')\n\t.option('-d, --description <desc>', 'Custom description override')\n\t.action(async (specPath: string, opts: Record<string, unknown>) => {\n\t\tconst maxTokens = Number(opts.maxTokens);\n\t\tif (Number.isNaN(maxTokens) || maxTokens < 0) {\n\t\t\tconsole.error('Error: --max-tokens must be a positive number');\n\t\t\tprocess.exitCode = 1;\n\t\t\treturn;\n\t\t}\n\n\t\tconst options: GenerateOptions = {\n\t\t\tname: opts.name as string | undefined,\n\t\t\toutDir: opts.out as string,\n\t\t\tmode: opts.mode as 'unified' | 'per-endpoint',\n\t\t\tmaxTokens,\n\t\t\tincludeExamples: opts.examples !== false,\n\t\t\tincludeErrorHandling: opts.errorHandling !== false,\n\t\t\tdescription: opts.description as string | undefined,\n\t\t};\n\n\t\tconst result = await generateFromOpenApi(resolve(specPath), options);\n\n\t\tif (!result.ok) {\n\t\t\tconsole.error(`Error: ${result.error}`);\n\t\t\tprocess.exitCode = 1;\n\t\t\treturn;\n\t\t}\n\n\t\tconst outDir = resolve(options.outDir ?? '.');\n\t\tlet written = 0;\n\n\t\tfor (const [filePath, content] of result.files) {\n\t\t\tconst fullPath = safeResolvePath(outDir, filePath);\n\t\t\tawait mkdir(dirname(fullPath), { recursive: true });\n\t\t\tawait writeFile(fullPath, content, 'utf-8');\n\t\t\tconsole.log(` Created: ${fullPath}`);\n\t\t\twritten++;\n\t\t}\n\n\t\tconsole.log('');\n\t\tconsole.log(`Generated ${written} skill file(s) from ${result.endpointCount} endpoints`);\n\t\tconsole.log(`Total tokens: ${result.tokenCount}`);\n\n\t\tif (result.tokenCount > maxTokens) {\n\t\t\tconsole.log('');\n\t\t\tconsole.log(\n\t\t\t\t`Warning: Generated content exceeds token budget (${result.tokenCount} > ${maxTokens}).`,\n\t\t\t);\n\t\t\tconsole.log('Consider using --mode per-endpoint to split into separate skills.');\n\t\t}\n\t});\n\nprogram\n\t.command('from-text')\n\t.description('Generate a basic SKILL.md from a name and description')\n\t.argument('<name>', 'Skill name')\n\t.argument('<description>', 'Skill description')\n\t.option('-o, --out <dir>', 'Output directory', '.')\n\t.option('-i, --instructions <text>', 'Additional instructions to include')\n\t.action(async (name: string, description: string, opts: Record<string, unknown>) => {\n\t\tconst result = generateFromText(name, description, opts.instructions as string | undefined);\n\n\t\tconst outDir = resolve((opts.out as string) ?? '.');\n\n\t\tfor (const [filePath, content] of result.files) {\n\t\t\tconst fullPath = safeResolvePath(outDir, filePath);\n\t\t\tawait mkdir(dirname(fullPath), { recursive: true });\n\t\t\tawait writeFile(fullPath, content, 'utf-8');\n\t\t\tconsole.log(` Created: ${fullPath}`);\n\t\t}\n\n\t\tconsole.log('');\n\t\tconsole.log(`Generated skill \"${name}\" (${result.tokenCount} tokens)`);\n\t});\n\n/**\n * Resolve a file path within a base directory, preventing path traversal.\n * Throws if the resolved path escapes the base directory.\n */\nfunction safeResolvePath(baseDir: string, filePath: string): string {\n\tconst resolved = resolve(baseDir, filePath);\n\tconst normalizedBase = resolve(baseDir);\n\tif (!resolved.startsWith(`${normalizedBase}/`) && resolved !== normalizedBase) {\n\t\tthrow new Error(`Path traversal detected: \"${filePath}\" escapes output directory`);\n\t}\n\treturn resolved;\n}\n\nprogram.parse();\n"],"mappings":"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","names":[]}
package/dist/index.cjs ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+ var _chunkPZXBN36Tcjs = require('./chunk-PZXBN36T.cjs');
8
+
9
+
10
+
11
+
12
+
13
+
14
+ exports.generateFromOpenApi = _chunkPZXBN36Tcjs.generateFromOpenApi; exports.generateFromSpec = _chunkPZXBN36Tcjs.generateFromSpec; exports.generateFromText = _chunkPZXBN36Tcjs.generateFromText; exports.parseOpenApi = _chunkPZXBN36Tcjs.parseOpenApi; exports.renderSkillMd = _chunkPZXBN36Tcjs.renderSkillMd;
15
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/skill-tools/skill-tools/packages/gen/dist/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACF,kTAAC","file":"/home/runner/work/skill-tools/skill-tools/packages/gen/dist/index.cjs"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Configuration for generating a SKILL.md file.
3
+ */
4
+ interface GenerateOptions {
5
+ /** Skill name (kebab-case, max 64 chars). Auto-derived if not provided. */
6
+ readonly name?: string;
7
+ /** Output directory path. Defaults to current working directory. */
8
+ readonly outDir?: string;
9
+ /** Generation mode: one skill per endpoint or one unified skill */
10
+ readonly mode?: 'unified' | 'per-endpoint';
11
+ /** Maximum token budget for the generated SKILL.md (default: 4000) */
12
+ readonly maxTokens?: number;
13
+ /** Include example requests/responses in the output */
14
+ readonly includeExamples?: boolean;
15
+ /** Include error handling section */
16
+ readonly includeErrorHandling?: boolean;
17
+ /** Custom description override */
18
+ readonly description?: string;
19
+ }
20
+ /**
21
+ * Intermediate representation of an API parsed from any source.
22
+ */
23
+ interface ApiSpec {
24
+ /** API title */
25
+ readonly title: string;
26
+ /** API description/summary */
27
+ readonly description: string;
28
+ /** Base URL(s) */
29
+ readonly servers: readonly string[];
30
+ /** API version */
31
+ readonly version: string;
32
+ /** Parsed endpoints */
33
+ readonly endpoints: readonly ApiEndpoint[];
34
+ /** Authentication schemes */
35
+ readonly auth: readonly AuthScheme[];
36
+ }
37
+ /**
38
+ * A single API endpoint.
39
+ */
40
+ interface ApiEndpoint {
41
+ /** HTTP method (GET, POST, etc.) */
42
+ readonly method: string;
43
+ /** URL path (e.g. /users/{id}) */
44
+ readonly path: string;
45
+ /** Operation ID (e.g. getUser) */
46
+ readonly operationId?: string;
47
+ /** Summary (short description) */
48
+ readonly summary?: string;
49
+ /** Detailed description */
50
+ readonly description?: string;
51
+ /** Tags for grouping */
52
+ readonly tags: readonly string[];
53
+ /** Path + query + header parameters */
54
+ readonly parameters: readonly ApiParameter[];
55
+ /** Request body schema */
56
+ readonly requestBody?: ApiRequestBody;
57
+ /** Response schemas by status code */
58
+ readonly responses: readonly ApiResponse[];
59
+ }
60
+ /**
61
+ * An API parameter (path, query, header, cookie).
62
+ */
63
+ interface ApiParameter {
64
+ /** Parameter name */
65
+ readonly name: string;
66
+ /** Location: path, query, header, cookie */
67
+ readonly in: 'path' | 'query' | 'header' | 'cookie';
68
+ /** Description */
69
+ readonly description?: string;
70
+ /** Whether the parameter is required */
71
+ readonly required: boolean;
72
+ /** Schema type (string, integer, etc.) */
73
+ readonly type?: string;
74
+ /** Example value */
75
+ readonly example?: unknown;
76
+ }
77
+ /**
78
+ * Request body definition.
79
+ */
80
+ interface ApiRequestBody {
81
+ /** Description */
82
+ readonly description?: string;
83
+ /** Content type (e.g. application/json) */
84
+ readonly contentType: string;
85
+ /** Whether the body is required */
86
+ readonly required: boolean;
87
+ /** Schema properties (flattened) */
88
+ readonly properties: readonly ApiProperty[];
89
+ /** Example body */
90
+ readonly example?: unknown;
91
+ }
92
+ /**
93
+ * A property in a request/response body.
94
+ */
95
+ interface ApiProperty {
96
+ /** Property name */
97
+ readonly name: string;
98
+ /** Schema type */
99
+ readonly type: string;
100
+ /** Description */
101
+ readonly description?: string;
102
+ /** Whether the property is required */
103
+ readonly required: boolean;
104
+ /** Example value */
105
+ readonly example?: unknown;
106
+ }
107
+ /**
108
+ * An API response definition.
109
+ */
110
+ interface ApiResponse {
111
+ /** HTTP status code (e.g. "200", "404") */
112
+ readonly statusCode: string;
113
+ /** Description */
114
+ readonly description?: string;
115
+ /** Content type */
116
+ readonly contentType?: string;
117
+ /** Schema properties (flattened) */
118
+ readonly properties: readonly ApiProperty[];
119
+ }
120
+ /**
121
+ * Authentication scheme.
122
+ */
123
+ interface AuthScheme {
124
+ /** Scheme type */
125
+ readonly type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
126
+ /** Scheme name */
127
+ readonly name: string;
128
+ /** Description */
129
+ readonly description?: string;
130
+ /** For apiKey: where the key goes */
131
+ readonly in?: 'header' | 'query' | 'cookie';
132
+ /** For http: scheme (bearer, basic) */
133
+ readonly scheme?: string;
134
+ }
135
+ /**
136
+ * Result of a generation operation.
137
+ */
138
+ interface GenerateResult {
139
+ /** Whether generation succeeded */
140
+ readonly ok: true;
141
+ /** Generated files (path → content) */
142
+ readonly files: ReadonlyMap<string, string>;
143
+ /** Number of endpoints processed */
144
+ readonly endpointCount: number;
145
+ /** Estimated token count of generated content */
146
+ readonly tokenCount: number;
147
+ }
148
+ /**
149
+ * Error result from generation.
150
+ */
151
+ interface GenerateError {
152
+ readonly ok: false;
153
+ readonly error: string;
154
+ }
155
+
156
+ /**
157
+ * Generate SKILL.md files from an OpenAPI specification file.
158
+ *
159
+ * @param specPath - Path to an OpenAPI 3.x spec (JSON or YAML)
160
+ * @param options - Generation options
161
+ * @returns Generation result with files map, or an error
162
+ */
163
+ declare function generateFromOpenApi(specPath: string, options?: GenerateOptions): Promise<GenerateResult | GenerateError>;
164
+ /**
165
+ * Generate SKILL.md files from a pre-parsed ApiSpec.
166
+ *
167
+ * @param spec - The API specification
168
+ * @param options - Generation options
169
+ * @returns Generation result with files map
170
+ */
171
+ declare function generateFromSpec(spec: ApiSpec, options?: GenerateOptions): GenerateResult | GenerateError;
172
+ /**
173
+ * Generate a SKILL.md from a simple text description.
174
+ *
175
+ * This is a lightweight alternative for when you don't have an OpenAPI spec.
176
+ * Produces a basic SKILL.md with the given name and description.
177
+ */
178
+ declare function generateFromText(name: string, description: string, instructions?: string): GenerateResult;
179
+
180
+ /**
181
+ * Parse an OpenAPI 3.x specification (JSON or YAML) into an ApiSpec.
182
+ *
183
+ * Supports OpenAPI 3.0.x and 3.1.x. Does not support Swagger 2.0.
184
+ * Resolves local `$ref` references within the document.
185
+ */
186
+ declare function parseOpenApi(content: string): ApiSpec;
187
+
188
+ /**
189
+ * Render a SKILL.md file from an ApiSpec.
190
+ *
191
+ * In "unified" mode, produces a single SKILL.md covering the entire API.
192
+ * In "per-endpoint" mode, produces one SKILL.md per endpoint.
193
+ */
194
+ declare function renderSkillMd(spec: ApiSpec, options?: GenerateOptions): Map<string, string>;
195
+
196
+ export { type ApiEndpoint, type ApiParameter, type ApiProperty, type ApiRequestBody, type ApiResponse, type ApiSpec, type AuthScheme, type GenerateError, type GenerateOptions, type GenerateResult, generateFromOpenApi, generateFromSpec, generateFromText, parseOpenApi, renderSkillMd };