
Create Evlog Adapter
Scaffold a typed evlog HTTP drain adapter so solo builders can ship custom wide-event sinks without reverse-engineering the package layout.
Overview
create-evlog-adapter is an agent skill for the Build phase that provides a complete TypeScript template for new evlog HTTP drain adapters using defineHttpDrain and resolveAdapterConfig.
Install
npx skills add https://github.com/hugorcd/evlog --skill create-evlog-adapterWhat is this skill?
- Complete TypeScript scaffold for packages/evlog/src/adapters/{name}.ts
- Uses defineHttpDrain and resolveAdapterConfig toolkit primitives
- FIELDS manifest drives resolveAdapterConfig and runtime-config-aware drain init
- Standard env keys: NUXT_{NAME}_API_KEY, {NAME}_API_KEY, endpoint, timeout
- Optional testable event transformation before encode
- Template covers config interface, FIELDS manifest, optional transformation, and HTTP drain wiring in one module
Adoption & trust: 714 installs on skills.sh; 1.4k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need a new evlog sink adapter but do not know the required config manifest, env var conventions, and drain shape the repo expects.
Who is it for?
Solo builders extending evlog with a custom logging or analytics HTTP destination in a TypeScript codebase.
Skip if: Teams that only need to set existing adapter env vars, or projects not using the evlog adapter toolkit primitives.
When should I use this skill?
You are adding a new evlog adapter file under packages/evlog/src/adapters and need the standard defineHttpDrain + resolveAdapterConfig structure.
What do I get? / Deliverables
You get a copy-ready adapter module with typed config, FIELDS-driven resolution, and HTTP drain wiring ready for tests and registration in the evlog package.
- New {name}.ts adapter module with typed config and FIELDS manifest
- HTTP drain registration ready for unit tests on encode/transform
Recommended Skills
Journey fit
Adapter authoring is implementation work that wires your app to an observability pipeline, which belongs on the Build shelf under integrations. The template targets third-party HTTP drains and env-based config—the classic integrations subphase, not greenfield UI or docs.
How it compares
Use as an in-repo scaffold template—not a generic OpenTelemetry exporter generator or a hosted observability MCP.
Common Questions / FAQ
Who is create-evlog-adapter for?
TypeScript developers maintaining or forking evlog who must add another HTTP drain adapter with the same config and typing patterns as existing sinks.
When should I use create-evlog-adapter?
During Build integrations when you are implementing packages/evlog/src/adapters/{name}.ts, defining FIELDS and env aliases, or adding a testable encode/transform step before shipping telemetry.
Is create-evlog-adapter safe to install?
It is documentation and codegen guidance only; review the Security Audits panel on this Prism page before trusting any related repo packages, and avoid pasting real API keys into agent chats.
SKILL.md
READMESKILL.md - Create Evlog Adapter
# Adapter Source Template Complete TypeScript template for `packages/evlog/src/adapters/{name}.ts` using the public toolkit primitives `defineHttpDrain` + `resolveAdapterConfig`. Replace `{Name}`, `{name}`, and `{NAME}` with the actual service name. ```typescript import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' import { resolveAdapterConfig } from '../shared/config' import { defineHttpDrain } from '../shared/drain' // --- 1. Config Interface ------------------------------------------------- // Service-specific fields. Standard names: apiKey, endpoint, serviceName, timeout. export interface {Name}Config { /** {Name} API key */ apiKey: string /** {Name} API endpoint. Default: https://api.{name}.com */ endpoint?: string /** Request timeout in milliseconds. Default: 5000 */ timeout?: number // Add service-specific fields here (dataset, project, region, etc.) } // Field manifest — drives both resolveAdapterConfig and runtime-config-aware // drain initialization. const FIELDS: ConfigField<{Name}Config>[] = [ { key: 'apiKey', env: ['NUXT_{NAME}_API_KEY', '{NAME}_API_KEY'] }, { key: 'endpoint', env: ['NUXT_{NAME}_ENDPOINT', '{NAME}_ENDPOINT'] }, { key: 'timeout' }, ] // --- 2. Event Transformation (optional) ---------------------------------- // If the service needs a specific shape, expose a converter so it's testable // independently. Otherwise pass `events` straight through in `encode`. export interface {Name}Event { timestamp: string level: string data: Record<string, unknown> } /** Convert a WideEvent to {Name}'s event format. */ export function to{Name}Event(event: WideEvent): {Name}Event { const { timestamp, level, ...rest } = event return { timestamp, level, data: rest } } // --- 3. Encode helper (pure, easy to test) ------------------------------- function build{Name}Payload(events: WideEvent[], config: {Name}Config) { const endpoint = (config.endpoint ?? 'https://api.{name}.com').replace(/\/$/, '') return { url: `${endpoint}/v1/ingest`, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.apiKey}`, }, body: JSON.stringify(events.map(to{Name}Event)), } } // --- 4. Direct send helpers ---------------------------------------------- // Exported for direct use and testability. /** Send a single event to {Name}. */ export async function sendTo{Name}(event: WideEvent, config: {Name}Config): Promise<void> { await sendBatchTo{Name}([event], config) } /** Send a batch of events to {Name}. */ export async function sendBatchTo{Name}( events: WideEvent[], config: {Name}Config, ): Promise<void> { if (events.length === 0) return const { url, headers, body } = build{Name}Payload(events, config) const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), config.timeout ?? 5000) try { const response = await fetch(url, { method: 'POST', headers, body, signal: controller.signal }) if (!response.ok) { const text = await response.text().catch(() => 'Unknown error') const safe = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text throw new Error(`{Name} API error: ${response.status} ${response.statusText} - ${safe}`) } } finally { clearTimeout(timeoutId) } } // --- 5. Factory built on `defineHttpDrain` ------------------------------ /** * Create a drain function for sending logs to {Name}. * * Configuration priority (highest to lowest): * 1. Overrides passed to create{Name}Drain() * 2. runtimeConfig.evlog.{name} * 3. runtimeConfig.{name} * 4. Environment variables: NUXT_{NAME}_*, {NAME}_* * * @example * ```ts * import { create{Name}Drain } from 'evlog/{name}' * * // Zero config — set NUXT_{NAME}_API_KEY env var * defineEvlog({ drain: create{Name}Drain() }) * * // With overrides * defineEvlog({ drain: create{Name}Drain({ apiKey: 'my-key' }) }) * ``` */ export function create{Nam