.claude/skills/template-generator/SKILL.md
Generate workflow templates with coherent node graphs and integration tests
npx skillsauth add dafthunk-com/dafthunk template-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate workflow templates: discover nodes, design graphs, wire edges correctly, create tests.
sourceOutput → targetInput (port names must match exactly)Directory structure:
packages/runtime/src/nodes/
├── input/ # TextInputNode, ImageInputNode, NumberInputNode...
├── preview/ # TextPreviewNode, ImagePreviewNode, NumberPreviewNode...
├── text/ # Summarization, translation, sentiment
├── image/ # Generation, manipulation
├── audio/ # Processing, transcription
├── anthropic/ # Claude models
├── openai/ # GPT models
├── logic/ # ConditionalForkNode, ConditionalJoinNode
└── ... # json/, math/, fetch/, browser/, etc.
Search commands:
Grep pattern="translate" path="apps/api/src/nodes" glob="*.ts"
Glob pattern="packages/runtime/src/nodes/text/*.ts"
Read node interface - look for nodeType.inputs and nodeType.outputs:
Read file_path="packages/runtime/src/nodes/text/bart-large-cnn-node.ts"
Key fields: inputs[].name → targetInput, outputs[].name → sourceOutput
The type field defines how the workflow is triggered:
| Type | Description | Entry Node |
|------|-------------|------------|
| manual | User-initiated via UI/API | Input nodes (TextInputNode, etc.) |
| email_message | Triggered by incoming email | ReceiveEmailNode |
| http_request | Triggered by HTTP request (sync) | HttpRequestNode |
| http_webhook | Triggered by webhook (async) | HttpRequestNode |
| scheduled | Triggered on schedule (cron) | ReceiveScheduledTriggerNode |
| queue_message | Triggered by queue message | ReceiveQueueMessageNode |
Finding trigger-compatible nodes: Nodes declare which triggers they work with via the compatibility field in their nodeType. Search for compatible nodes:
Grep pattern="compatibility:.*email_message" path="apps/api/src/nodes" glob="*.ts"
Grep pattern="compatibility:.*http_request" path="apps/api/src/nodes" glob="*.ts"
File: apps/api/src/templates/{template-id}.ts
import type { WorkflowTemplate } from "@dafthunk/types";
import { TextInputNode } from "../nodes/input/text-input-node";
import { BartLargeCnnNode } from "../nodes/text/bart-large-cnn-node";
import { TextPreviewNode } from "../nodes/preview/text-preview-node";
export const myTemplate: WorkflowTemplate = {
id: "my-template",
name: "My Template",
description: "What it does",
icon: "file-text",
type: "manual",
tags: ["text", "ai"],
nodes: [
TextInputNode.create({
id: "text-to-process",
name: "Text to Process",
position: { x: 100, y: 100 },
inputs: { value: "Sample text...", rows: 4 },
}),
BartLargeCnnNode.create({
id: "summarizer",
name: "Summarizer",
position: { x: 500, y: 100 },
}),
TextPreviewNode.create({
id: "result",
name: "Summary",
position: { x: 900, y: 100 },
}),
],
edges: [
{ source: "text-to-process", target: "summarizer", sourceOutput: "value", targetInput: "inputText" },
{ source: "summarizer", target: "result", sourceOutput: "summary", targetInput: "value" },
],
};
Positioning: Inputs at x:100, processing at x:500, outputs at x:900. Stack vertically with 200px spacing.
Naming: IDs are kebab-case (text-to-translate). Names are short Title Case, omit "Preview" for outputs.
ConditionalForkNode - splits flow based on boolean:
condition (boolean), value (any)true, false (only ONE has value)ConditionalJoinNode - merges exclusive branches:
a, b (exactly ONE must have value)result[BooleanInput] ──condition──► [Fork] ──true──► [ProcessorA] ──►┐
[TextInput] ────value──────► ──false─► [ProcessorB] ──►├─► [Join] ──► [Preview]
Register in apps/api/src/templates/index.ts:
import { myTemplate } from "./my-template";
export const workflowTemplates = [..., myTemplate];
Test file {template-id}.integration.ts:
describe("My Template", () => {
it("should have valid structure", () => {
expect(myTemplate.nodes).toHaveLength(3);
expect(myTemplate.edges).toHaveLength(2);
const nodeIds = new Set(myTemplate.nodes.map(n => n.id));
for (const edge of myTemplate.edges) {
expect(nodeIds.has(edge.source)).toBe(true);
expect(nodeIds.has(edge.target)).toBe(true);
}
});
});
Run: pnpm typecheck && pnpm --filter '@dafthunk/api' test {template-id}
| Output | Compatible Inputs | |--------|------------------| | string | string, any | | number | number, any | | boolean | boolean, any | | image | image, blob, any | | audio | audio, blob, any | | json | json, any |
testing
Generate new workflow nodes with implementation, tests, and registry registration
development
Generate new OAuth integration providers for Dafthunk with backend providers, type definitions, frontend configurations, and integration nodes
testing
Create, edit, improve, or audit AgentSkills. Use when creating a new skill from scratch or when asked to improve, review, audit, tidy up, or clean up an existing skill or SKILL.md file. Also use when editing or restructuring a skill directory (moving files to references/ or scripts/, removing stale content, validating against the AgentSkills spec). Triggers on phrases like "create a skill", "author a skill", "tidy up a skill", "improve this skill", "review the skill", "clean up the skill", "audit the skill".
testing
Host security hardening and risk-tolerance configuration for OpenClaw deployments. Use when a user asks for security audits, firewall/SSH/update hardening, risk posture, exposure review, OpenClaw cron scheduling for periodic checks, or version status checks on a machine running OpenClaw (laptop, workstation, Pi, VPS).