.claude/skills/node-generator/SKILL.md
Generate new workflow nodes with implementation, tests, and registry registration
npx skillsauth add dafthunk-com/dafthunk node-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 nodes for Dafthunk: research requirements, create implementation and tests, register in the node registry.
When a user requests a new node, research first, then present a complete specification for confirmation.
Research the functionality:
apps/api/package.json or search npm for the latest versionCheck existing patterns:
packages/runtime/src/nodes/<category>/ for similar nodesDraft complete requirements:
Present for confirmation:
Based on [library/API/functionality], here's the proposed node:
**Name**: [Node Name]
**ID**: `node-id`
**Category**: category
**Icon**: icon-name
**Inputs**:
- `inputName` (type, required/optional): Description
**Outputs**:
- `outputName` (type): Description
- `metadata` (type, hidden): Description
**Dependencies**:
- package-name@^version
**Tags**: Category, Tag1, Tag2
Does this match your requirements?
Only ask for information you cannot reasonably infer or research. The goal is to present a complete, research-backed specification that the user only needs to approve or tweak.
File: packages/runtime/src/nodes/<category>/<node-id>.ts
import { NodeExecution, NodeType } from "@dafthunk/types";
import { ExecutableNode, NodeContext } from "../../runtime/node-types";
export class [NodeClassName]Node extends ExecutableNode {
public static readonly nodeType: NodeType = {
id: "[node-id]",
name: "[Node Display Name]",
type: "[node-id]",
description: "[One-line description]",
tags: ["Category", "Tag1", "Tag2"],
icon: "[icon-name]",
documentation: "[Detailed documentation]",
inlinable: false,
asTool: false,
inputs: [
{
name: "[inputName]",
type: "[type]",
description: "[Description]",
required: true,
repeated: false,
},
],
outputs: [
{
name: "[outputName]",
type: "[type]",
description: "[Description]",
},
],
};
public async execute(context: NodeContext): Promise<NodeExecution> {
try {
const { input1, optionalInput = "default" } = context.inputs;
// Validate required inputs
if (input1 === null || input1 === undefined) {
return this.createErrorResult("Missing required input: input1");
}
if (typeof input1 !== "expectedType") {
return this.createErrorResult(
`Invalid input type for input1: expected expectedType, got ${typeof input1}`
);
}
// Handle repeated inputs (arrays)
if (Array.isArray(input1)) {
for (let i = 0; i < input1.length; i++) {
if (typeof input1[i] !== "string") {
return this.createErrorResult(
`Invalid input at position ${i}: expected string, got ${typeof input1[i]}`
);
}
}
}
// Main logic
const result = processInput(input1);
return this.createSuccessResult({ output1: result });
} catch (err) {
const error = err as Error;
return this.createErrorResult(`Error in [NodeName]: ${error.message}`);
}
}
}
Defensive programming checklist:
File: packages/runtime/src/nodes/<category>/<node-id>.test.ts
import { Node } from "@dafthunk/types";
import { describe, expect, it } from "vitest";
import { NodeContext } from "../../runtime/node-types";
import { [NodeClassName]Node } from "./<node-id>";
describe("[NodeClassName]Node", () => {
const createContext = (inputs: Record<string, unknown>): NodeContext => ({
nodeId: "[node-id]",
inputs,
getIntegration: async () => { throw new Error("No integrations in test"); },
env: {},
} as unknown as NodeContext);
it("should [perform expected operation]", async () => {
const node = new [NodeClassName]Node({ nodeId: "[node-id]" } as unknown as Node);
const result = await node.execute(createContext({ input1: "test value" }));
expect(result.status).toBe("completed");
expect(result.outputs?.output1).toBe("expected value");
});
it("should handle empty input", async () => {
const node = new [NodeClassName]Node({ nodeId: "[node-id]" } as unknown as Node);
const result = await node.execute(createContext({ input1: "" }));
expect(result.status).toBe("completed");
});
it("should return error for missing input", async () => {
const node = new [NodeClassName]Node({ nodeId: "[node-id]" } as unknown as Node);
const result = await node.execute(createContext({}));
expect(result.status).toBe("error");
expect(result.error).toContain("Missing required input");
});
it("should return error for invalid type", async () => {
const node = new [NodeClassName]Node({ nodeId: "[node-id]" } as unknown as Node);
const result = await node.execute(createContext({ input1: 123 }));
expect(result.status).toBe("error");
expect(result.error).toContain("Invalid input type");
});
it("should handle array of inputs", async () => {
const node = new [NodeClassName]Node({ nodeId: "[node-id]" } as unknown as Node);
const result = await node.execute(createContext({ input1: ["val1", "val2"] }));
expect(result.status).toBe("completed");
});
it("should return error for invalid element in array", async () => {
const node = new [NodeClassName]Node({ nodeId: "[node-id]" } as unknown as Node);
const result = await node.execute(createContext({ input1: ["valid", 123] }));
expect(result.status).toBe("error");
expect(result.error).toContain("position 1");
});
});
Test coverage: Happy path, edge cases (empty/boundary values), error cases (missing/wrong types), array handling, type coercion (if applicable), domain-specific cases.
File: apps/api/src/runtime/cloudflare-node-registry.ts
Add import (alphabetically within category):
import { [NodeClassName]Node } from "./<category>/<node-id>";
Register in constructor (alphabetically within category):
this.registerImplementation([NodeClassName]Node);
pnpm typecheck
pnpm --filter '@dafthunk/api' test <node-id>
List files created, confirm registry registration, show test command, note any dependencies to install.
Repeated inputs (single value or array):
if (typeof values === "string") { /* handle single */ }
if (Array.isArray(values)) { /* validate each element */ }
Number coercion:
const num = Number(input);
if (isNaN(num)) { return this.createErrorResult("Invalid number"); }
Optional inputs:
const { required, optional = "default" } = context.inputs;
External libraries:
try {
const result = library.function(input);
} catch (err) {
return this.createErrorResult(`Operation failed: ${(err as Error).message}`);
}
testing
Generate workflow templates with coherent node graphs and integration tests
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).