
create-agent
Bootstrap a modular AI agent with OpenRouter SDK, extensible hooks, and optional Ink TUI
- Installs
- 2.7K
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/openrouterarchived/agent-skills --skill create-agent- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads create-agent automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Build a Modular AI Agent with OpenRouter
This skill helps you create a modular AI agent with:
- Standalone Agent Core - Runs independently, extensible via hooks
- OpenRouter SDK - Unified access to 300+ language models
- Optional Ink TUI - Beautiful terminal UI (separate from agent logic)
Architecture
┌─────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Ink TUI │ │ HTTP API │ │ Discord │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Agent Core │ │
│ │ (hooks & lifecycle) │ │
│ └───────────┬───────────┘ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ OpenRouter SDK │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────────────┘
Prerequisites
Get an OpenRouter API key at: https://openrouter.ai/settings/keys
⚠️ Security: Never commit API keys. Use environment variables.
Project Setup
Step 1: Initialize Project
mkdir my-agent && cd my-agent
npm init -y
npm pkg set type="module"
Step 2: Install Dependencies
npm install @openrouter/sdk zod eventemitter3
npm install ink react # Optional: only for TUI
npm install -D typescript @types/react tsx
Step 3: Create tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src"]
}
Step 4: Add Scripts to package.json
{
"scripts": {
"start": "tsx src/cli.tsx",
"start:headless": "tsx src/headless.ts",
"dev": "tsx watch src/cli.tsx"
}
}
File Structure
src/
├── agent.ts # Standalone agent core with hooks
├── tools.ts # Tool definitions
├── cli.tsx # Ink TUI (optional interface)
└── headless.ts # Headless usage example
Step 1: Agent Core with Hooks
Create src/agent.ts - the standalone agent that can run anywhere:
import { OpenRouter, tool, stepCountIs } from '@openrouter/sdk';
import type { Tool, StopCondition, StreamableOutputItem } from '@openrouter/sdk';
import { EventEmitter } from 'eventemitter3';
import { z } from 'zod';
// Message types
export interface Message {
role: 'user' | 'assistant' | 'system';
content: string;
}
// Agent events for hooks (items-based streaming model)
export interface AgentEvents {
'message:user': (message: Message) => void;
'message:assistant': (message: Message) => void;
'item:update': (item: StreamableOutputItem) => void; // Items emitted with same ID, replace by ID
'stream:start': () => void;
'stream:delta': (delta: string, accumulated: string) => void;
'stream:end': (fullText: string) => void;
'tool:call': (name: string, args: unknown) => void;
'tool:result': (name: string, result: unknown) => void;
'reasoning:update': (text: string) => void; // Extended thinking content
'error': (error: Error) => void;
'thinking:start': () => void;
'thinking:end': () => void;
}
// Agent configuration
export interface AgentConfig {
apiKey: string;
model?: string;
instructions?: string;
tools?: Tool<z.ZodTypeAny, z.ZodTypeAny>[];
maxSteps?: number;
}
// The Agent class - runs independently of any UI
export class Agent extends EventEmitter<AgentEvents> {
private client: OpenRouter;
private messages: Message[] = [];
private config: Required<Omit<AgentConfig, 'apiKey'>> & { apiKey: string };
constructor(config: AgentConfig) {
super();
this.client = new OpenRouter({ apiKey: config.apiKey });
this.config = {
apiKey: config.apiKey,
model: config.model ?? 'openrouter/auto',
instructions: config.instructions ?? 'You are a helpful assistant.',
tools: config.tools ?? [],
maxSteps: config.maxSteps ?? 5,
};
}
// Get conversation history
getMessages(): Message[] {
return [...this.messages];
}
// Clear conversation
clearHistory(): void {
this.messages = [];
}
// Add a system message
setInstructions(instructions: string): void {
this.config.instructions = instructions;
}
// Register additional tools at runtime
addTool(newTool: Tool<z.ZodTypeAny, z.ZodTypeAny>): void {
this.config.tools.push(newTool);
}
// Send a message and get streaming response using items-based model
// Items are emitted multiple times with the same ID but progressively updated content
// Replace items by their ID rather than accumulating chunks
async send(content: string): Promise<string> {
const userMessage: Message = { role: 'user', content };
this.messages.push(userMessage);
this.emit('message:user', userMessage);
this.emit('thinking:start');
try {
const result = this.client.callModel({
model: this.config.model,
instructions: this.config.instructions,
input: this.messages.map((m) => ({ role: m.role, content: m.content })),
tools: this.config.tools.length > 0 ? this.config.tools : undefined,
stopWhen: [stepCountIs(this.config.maxSteps)],
});
this.emit('stream:start');
let fullText = '';
// Use getItemsStream() for items-based streaming (recommended)
// Each item emission is complete - replace by ID, don't accumulate
for await (const item of result.getItemsStream()) {
// Emit the item for UI state management (use Map keyed by item.id)
this.emit('item:update', item);
switch (item.type) {
case 'message':
// Message items contain progressively updated content
const textContent = item.content?.find((c: { type: string }) => c.type === 'output_text');
if (textContent && 'text' in textContent) {
const newText = textContent.text;
if (newText !== fullText) {
const delta = newText.slice(fullText.length);
fullText = newText;
this.emit('stream:delta', delta, fullText);
}
}
break;
case 'function_call':
// Function call arguments stream progressively
if (item.status === 'completed') {
this.emit('tool:call', item.name, JSON.parse(item.arguments || '{}'));
}
break;
case 'function_call_output':
this.emit('tool:result', item.callId, item.output);
break;
case 'reasoning':
// Extended thinking/reasoning content
const reasoningText = item.content?.find((c: { type: string }) => c.type === 'reasoning_text');
if (reasoningText && 'text' in reasoningText) {
this.emit('reasoning:update', reasoningText.text);
}
break;
// Additional item types: web_search_call, file_search_call, image_generation_call
}
}
// Get final text if streaming didn't capture it
if (!fullText) {
fullText = await result.getText();
}
this.emit('stream:end', fullText);
const assistantMessage: Message = { role: 'assistant', content: fullText };
this.messages.push(assistantMessage);
this.emit('message:assistant', assistantMessage);
return fullText;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
this.emit('error', error);
throw error;
} finally {
this.emit('thinking:end');
}
}
// Send without streaming (simpler for programmatic use)
async sendSync(content: string): Promise<string> {
const userMessage: Message = { role: 'user', content };
this.messages.push(userMessage);
this.emit('message:user', userMessage);
try {
const result = this.client.callModel({
model: this.config.model,
instructions: this.config.instructions,
input: this.messages.map((m) => ({ role: m.role, content: m.content })),
tools: this.config.tools.length > 0 ? this.config.tools : undefined,
stopWhen: [stepCountIs(this.config.maxSteps)],
});
const fullText = await result.getText();
const assistantMessage: Message = { role: 'assistant', content: fullText };
this.messages.push(assistantMessage);
this.emit('message:assistant', assistantMessage);
return fullText;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
this.emit('error', error);
throw error;
}
}
}
// Factory function for easy creation
export function createAgent(config: AgentConfig): Agent {
return new Agent(config);
}
Step 2: Define Tools
Create src/tools.ts:
import { tool } from '@openrouter/sdk';
import { z } from 'zod';
export const timeTool = tool({
name: 'get_current_time',
description: 'Get the current date and time',
inputSchema: z.object({
timezone: z.string().optional().describe('Timezone (e.g., "UTC", "America/New_York")'),
}),
execute: async ({ timezone }) => {
return {
time: new Date().toLocaleString('en-US', { timeZone: timezone || 'UTC' }),
timezone: timezone || 'UTC',
};
},
});
export const calculatorTool = tool({
name: 'calculate',
description: 'Perform mathematical calculations',
inputSchema: z.object({
expression: z.string().describe('Math expression (e.g., "2 + 2", "sqrt(16)")'),
}),
execute: async ({ expression }) => {
// Simple safe eval for basic math
const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, '');
const result = Function(`"use strict"; return (${sanitized})`)();
return { expression, result };
},
});
export const defaultTools = [timeTool, calculatorTool];
Step 3: Headless Usage (No UI)
Create src/headless.ts - use the agent programmatically:
import { createAgent } from './agent.js';
import { defaultTools } from './tools.js';
async function main() {
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: 'openrouter/auto',
instructions: 'You are a helpful assistant with access to tools.',
tools: defaultTools,
});
// Hook into events
agent.on('thinking:start', () => console.log('\n🤔 Thinking...'));
agent.on('tool:call', (name, args) => console.log(`🔧 Using ${name}:`, args));
agent.on('stream:delta', (delta) => process.stdout.write(delta));
agent.on('stream:end', () => console.log('\n'));
agent.on('error', (err) => console.error('❌ Error:', err.message));
// Interactive loop
const readline = await import('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log('Agent ready. Type your message (Ctrl+C to exit):\n');
const prompt = () => {
rl.question('You: ', async (input) => {
if (!input.trim()) {
prompt();
return;
}
await agent.send(input);
prompt();
});
};
prompt();
}
main().catch(console.error);
Run headless: OPENROUTER_API_KEY=sk-or-... npm run start:headless
Step 4: Ink TUI (Optional Interface)
Create src/cli.tsx - a beautiful terminal UI that uses the agent with items-based streaming:
import React, { useState, useEffect, useCallback } from 'react';
import { render, Box, Text, useInput, useApp } from 'ink';
import type { StreamableOutputItem } from '@openrouter/sdk';
import { createAgent, type Agent, type Message } from './agent.js';
import { defaultTools } from './tools.js';
// Initialize agent (runs independently of UI)
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: 'openrouter/auto',
instructions: 'You are a helpful assistant. Be concise.',
tools: defaultTools,
});
function ChatMessage({ message }: { message: Message }) {
const isUser = message.role === 'user';
return (
<Box flexDirection="column" marginBottom={1}>
<Text bold color={isUser ? 'cyan' : 'green'}>
{isUser ? '▶ You' : '◀ Assistant'}
</Text>
<Text wrap="wrap">{message.content}</Text>
</Box>
);
}
// Render streaming items by type using the items-based pattern
function ItemRenderer({ item }: { item: StreamableOutputItem }) {
switch (item.type) {
case 'message': {
const textContent = item.content?.find((c: { type: string }) => c.type === 'output_text');
const text = textContent && 'text' in textContent ? textContent.text : '';
return (
<Box flexDirection="column" marginBottom={1}>
<Text bold color="green">◀ Assistant</Text>
<Text wrap="wrap">{text}</Text>
{item.status !== 'completed' && <Text color="gray">▌</Text>}
</Box>
);
}
case 'function_call':
return (
<Text color="yellow">
{item.status === 'completed' ? ' ✓' : ' 🔧'} {item.name}
{item.status === 'in_progress' && '...'}
</Text>
);
case 'reasoning': {
const reasoningText = item.content?.find((c: { type: string }) => c.type === 'reasoning_text');
const text = reasoningText && 'text' in reasoningText ? reasoningText.text : '';
return (
<Box flexDirection="column" marginBottom={1}>
<Text bold color="magenta">💭 Thinking</Text>
<Text wrap="wrap" color="gray">{text}</Text>
</Box>
);
}
default:
return null;
}
}
function InputField({
value,
onChange,
onSubmit,
disabled,
}: {
value: string;
onChange: (v: string) => void;
onSubmit: () => void;
disabled: boolean;
}) {
useInput((input, key) => {
if (disabled) return;
if (key.return) onSubmit();
else if (key.backspace || key.delete) onChange(value.slice(0, -1));
else if (input && !key.ctrl && !key.meta) onChange(value + input);
});
return (
<Box>
<Text color="yellow">{'> '}</Text>
<Text>{value}</Text>
<Text color="gray">{disabled ? ' ···' : '█'}</Text>
</Box>
);
}
function App() {
const { exit } = useApp();
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
// Use Map keyed by item ID for efficient React state updates (items-based pattern)
const [items, setItems] = useState<Map<string, StreamableOutputItem>>(new Map());
useInput((_, key) => {
if (key.escape) exit();
});
// Subscribe to agent events using items-based streaming
useEffect(() => {
const onThinkingStart = () => {
setIsLoading(true);
setItems(new Map()); // Clear items for new response
};
// Items-based streaming: replace items by ID, don't accumulate
const onItemUpdate = (item: StreamableOutputItem) => {
setItems((prev) => new Map(prev).set(item.id, item));
};
const onMessageAssistant = () => {
setMessages(agent.getMessages());
setItems(new Map()); // Clear streaming items
setIsLoading(false);
};
const onError = (err: Error) => {
setIsLoading(false);
};
agent.on('thinking:start', onThinkingStart);
agent.on('item:update', onItemUpdate);
agent.on('message:assistant', onMessageAssistant);
agent.on('error', onError);
return () => {
agent.off('thinking:start', onThinkingStart);
agent.off('item:update', onItemUpdate);
agent.off('message:assistant', onMessageAssistant);
agent.off('error', onError);
};
}, []);
const sendMessage = useCallback(async () => {
if (!input.trim() || isLoading) return;
const text = input.trim();
setInput('');
setMessages((prev) => [...prev, { role: 'user', content: text }]);
await agent.send(text);
}, [input, isLoading]);
return (
<Box flexDirection="column" padding={1}>
<Box marginBottom={1}>
<Text bold color="magenta">🤖 OpenRouter Agent</Text>
<Text color="gray"> (Esc to exit)</Text>
</Box>
<Box flexDirection="column" marginBottom={1}>
{/* Render completed messages */}
{messages.map((msg, i) => (
<ChatMessage key={i} message={msg} />
))}
{/* Render streaming items by type (items-based pattern) */}
{Array.from(items.values()).map((item) => (
<ItemRenderer key={item.id} item={item} />
))}
</Box>
<Box borderStyle="single" borderColor="gray" paddingX={1}>
<InputField
value={input}
onChange={setInput}
onSubmit={sendMessage}
disabled={isLoading}
/>
</Box>
</Box>
);
}
render(<App />);
Run TUI: OPENROUTER_API_KEY=sk-or-... npm start
Understanding Items-Based Streaming
The OpenRouter SDK uses an items-based streaming model - a key paradigm where items are emitted multiple times with the same ID but progressively updated content. Instead of accumulating chunks, you replace items by their ID.
How It Works
Each iteration of getItemsStream() yields a complete item with updated content:
// Iteration 1: Partial message
{ id: "msg_123", type: "message", content: [{ type: "output_text", text: "Hello" }] }
// Iteration 2: Updated message (replace, don't append)
{ id: "msg_123", type: "message", content: [{ type: "output_text", text: "Hello world" }] }
For function calls, arguments stream progressively:
// Iteration 1: Partial arguments
{ id: "call_456", type: "function_call", name: "get_weather", arguments: "{\"q" }
// Iteration 2: Complete arguments
{ id: "call_456", type: "function_call", name: "get_weather", arguments: "{\"query\": \"Paris\"}", status: "completed" }
Why Items Are Better
Traditional (accumulation required):
let text = '';
for await (const chunk of result.getTextStream()) {
text += chunk; // Manual accumulation
updateUI(text);
}
Items (complete replacement):
const items = new Map<string, StreamableOutputItem>();
for await (const item of result.getItemsStream()) {
items.set(item.id, item); // Replace by ID
updateUI(items);
}
Benefits:
- No manual chunk management - each item is complete
- Handles concurrent outputs - function calls and messages can stream in parallel
- Full TypeScript inference for all item types
- Natural Map-based state works perfectly with React/UI frameworks
Extending the Agent
Add Custom Hooks
const agent = createAgent({ apiKey: '...' });
// Log all events
agent.on('message:user', (msg) => {
saveToDatabase('user', msg.content);
});
agent.on('message:assistant', (msg) => {
saveToDatabase('assistant', msg.content);
sendWebhook('new_message', msg);
});
agent.on('tool:call', (name, args) => {
analytics.track('tool_used', { name, args });
});
agent.on('error', (err) => {
errorReporting.capture(err);
});
Use with HTTP Server
import express from 'express';
import { createAgent } from './agent.js';
const app = express();
app.use(express.json());
// One agent per session (store in memory or Redis)
const sessions = new Map<string, Agent>();
app.post('/chat', async (req, res) => {
const { sessionId, message } = req.body;
let agent = sessions.get(sessionId);
if (!agent) {
agent = createAgent({ apiKey: process.env.OPENROUTER_API_KEY! });
sessions.set(sessionId, agent);
}
const response = await agent.sendSync(message);
res.json({ response, history: agent.getMessages() });
});
app.listen(3000);
Use with Discord
import { Client, GatewayIntentBits } from 'discord.js';
import { createAgent } from './agent.js';
const discord = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
});
const agents = new Map<string, Agent>();
discord.on('messageCreate', async (msg) => {
if (msg.author.bot) return;
let agent = agents.get(msg.channelId);
if (!agent) {
agent = createAgent({ apiKey: process.env.OPENROUTER_API_KEY! });
agents.set(msg.channelId, agent);
}
const response = await agent.sendSync(msg.content);
await msg.reply(response);
});
discord.login(process.env.DISCORD_TOKEN);
Agent API Reference
Constructor Options
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | OpenRouter API key |
| model | string | 'openrouter/auto' | Model to use |
| instructions | string | 'You are a helpful assistant.' | System prompt |
| tools | Tool[] | [] | Available tools |
| maxSteps | number | 5 | Max agentic loop iterations |
Methods
| Method | Returns | Description |
|---|---|---|
send(content) | Promise<string> | Send message with streaming |
sendSync(content) | Promise<string> | Send message without streaming |
getMessages() | Message[] | Get conversation history |
clearHistory() | void | Clear conversation |
setInstructions(text) | void | Update system prompt |
addTool(tool) | void | Add tool at runtime |
Events
| Event | Payload | Description |
|---|---|---|
message:user | Message | User message added |
message:assistant | Message | Assistant response complete |
item:update | StreamableOutputItem | Item emitted (replace by ID, don't accumulate) |
stream:start | - | Streaming started |
stream:delta | (delta, accumulated) | New text chunk |
stream:end | fullText | Streaming complete |
tool:call | (name, args) | Tool being called |
tool:result | (name, result) | Tool returned result |
reasoning:update | text | Extended thinking content |
thinking:start | - | Agent processing |
thinking:end | - | Agent done processing |
error | Error | Error occurred |
Item Types (from getItemsStream)
The SDK uses an items-based streaming model where items are emitted multiple times with the same ID but progressively updated content. Replace items by their ID rather than accumulating chunks.
| Type | Purpose |
|---|---|
message | Assistant text responses |
function_call | Tool invocations with streaming arguments |
function_call_output | Results from executed tools |
reasoning | Extended thinking content |
web_search_call | Web search operations |
file_search_call | File search operations |
image_generation_call | Image generation operations |
Discovering Models
Do not hardcode model IDs - they change frequently. Use the models API:
Fetch Available Models
interface OpenRouterModel {
id: string;
name: string;
description?: string;
context_length: number;
pricing: { prompt: string; completion: string };
top_provider?: { is_moderated: boolean };
}
async function fetchModels(): Promise<OpenRouterModel[]> {
const res = await fetch('https://openrouter.ai/api/v1/models');
const data = await res.json();
return data.data;
}
// Find models by criteria
async function findModels(filter: {
author?: string; // e.g., 'anthropic', 'openai', 'google'
minContext?: number; // e.g., 100000 for 100k context
maxPromptPrice?: number; // e.g., 0.001 for cheap models
}): Promise<OpenRouterModel[]> {
const models = await fetchModels();
return models.filter((m) => {
if (filter.author && !m.id.startsWith(filter.author + '/')) return false;
if (filter.minContext && m.context_length < filter.minContext) return false;
if (filter.maxPromptPrice) {
const price = parseFloat(m.pricing.prompt);
if (price > filter.maxPromptPrice) return false;
}
return true;
});
}
// Example: Get latest Claude models
const claudeModels = await findModels({ author: 'anthropic' });
console.log(claudeModels.map((m) => m.id));
// Example: Get models with 100k+ context
const longContextModels = await findModels({ minContext: 100000 });
// Example: Get cheap models
const cheapModels = await findModels({ maxPromptPrice: 0.0005 });
Dynamic Model Selection in Agent
// Create agent with dynamic model selection
const models = await fetchModels();
const bestModel = models.find((m) => m.id.includes('claude')) || models[0];
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: bestModel.id, // Use discovered model
instructions: 'You are a helpful assistant.',
});
Using openrouter/auto
For simplicity, use openrouter/auto which automatically selects the best
available model for your request:
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: 'openrouter/auto', // Auto-selects best model
});
Models API Reference
- Endpoint:
GET https://openrouter.ai/api/v1/models - Response:
{ data: OpenRouterModel[] } - Browse models: https://openrouter.ai/models
Resources
- OpenRouter Docs: https://openrouter.ai/docs
- Models API: https://openrouter.ai/api/v1/models
- Ink Docs: https://github.com/vadimdemedes/ink
- Get API Key: https://openrouter.ai/settings/keys
FAQ
What does the create-agent skill do?
Bootstrap a modular AI agent with OpenRouter SDK, extensible hooks, and optional Ink TUI
How do I install create-agent?
Run npx skills add https://github.com/openrouterarchived/agent-skills --skill create-agent in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/create-agent/ (personal) or .claude/skills/create-agent/ (project).
Which AI agents can use create-agent?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is create-agent safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains create-agent?
It is published by openrouterarchived in openrouterarchived/agent-skills. It has 2,702 recorded installs.
More from openrouterarchived/agent-skills
openrouter-typescript-sdk
openrouterarchived/agent-skills
Complete reference for integrating with 300+ AI models through the OpenRouter TypeScript SDK using the callModel pattern
Popular ai & agents skills
google-agents-cli-publish
google/agents-cli
This skill should be used when the user wants to "publish an agent", "publish my ADK agent", "register an agent with Gemini", "publish to Gemini" (formerly Gemini Enterprise), or needs guidance on the agents-cli publish gemini command. Also use when the user wants to "manage agents in Agent Registry", "list/update/delete registered agents", or "register an MCP server". Covers ADK vs A2A registration modes, programmatic and interactive usage, flag reference, auto-detection from deployment metadata, Agent Registry fleet management, and troubleshooting. Part of the agents-cli skills suite. Do NOT use for deployment (use google-agents-cli-deploy).
google-agents-cli-eval
google/agents-cli
This skill should be used when the user wants to "run an evaluation", "evaluate my agent", "evaluate my ADK agent", "write an eval dataset", "analyze eval failures", "compare eval results", "optimize agent", or needs guidance on the Gemini platform eval methodology and the Quality Flywheel. Covers eval metrics, dataset schema, LLM-as-judge scoring, and common failure causes. Applies to any agents-cli project, whatever framework the agent is written in. Do NOT use for agent API code patterns (ADK: use google-agents-cli-adk-code), deployment (use google-agents-cli-deploy), or project scaffolding (use google-agents-cli-scaffold).
skill-creator
anthropics/skills
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
codex-pet
prime-skills/runcomfy-agent-skills
Codex Pet generator on RunComfy. Build a Codex-compatible Codex Pet spritesheet.webp + pet.json from a single reference image, drop it into ${CODEXHOME:-$HOME/.codex}/pets/ / and Codex picks it up as a custom Codex Pet next to the 8 built-ins. This skill produces the exact Codex Pet atlas Codex expects (1536x1872 PNG/WebP, 8 cols x 9 rows, 192x208 cells, 9 animation states — idle, running-right, running-left, waving, jumping, failed, waiting, running, review). Calls OpenAI GPT Image 2 edit ONCE via the local RunComfy CLI as runcomfy run openai/gpt-image-2/edit to produce a canonical Codex Pet pose, then assembles all 9 animation rows programmatically with ImageMagick micro-transforms — no Codex Pro, no $imagegen, no OPENAIAPIKEY required, only RUNCOMFYTOKEN. Triggers on "codex pet", "create codex pet", "make codex pet", "hatch codex pet", "/hatch image", "desktop pet codex", "codex pets", "spritesheet.webp", or any explicit ask to build a custom pet for OpenAI Codex.
full-output-enforcement
leonxlnx/taste-skill
Overrides default LLM truncation behavior. Enforces complete code generation, bans placeholder patterns, and handles token-limit splits cleanly. Apply to any task requiring exhaustive, unabridged output.
executing-plans
obra/superpowers
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available