workflow-generator
YOU MUST USE THIS SKILL when the user wants to CREATE or BUILD a NEW workflow automation. Activate for requests like: 'create a workflow', 'build a workflow', 'generate a workflow', 'make a workflow', 'I want to automate', 'automate X to Y', 'schedule a task', 'monitor X and send to Y'. This skill creates workflows from simple YAML plans with automatic validation and import.
SKILL.md
| Name | workflow-generator |
| Description | YOU MUST USE THIS SKILL when the user wants to CREATE or BUILD a NEW workflow automation. Activate for requests like: 'create a workflow', 'build a workflow', 'generate a workflow', 'make a workflow', 'I want to automate', 'automate X to Y', 'schedule a task', 'monitor X and send to Y'. This skill creates workflows from simple YAML plans with automatic validation and import. |
name: workflow-generator description: "YOU MUST USE THIS SKILL when the user wants to CREATE or BUILD a NEW workflow automation. Activate for requests like: 'create a workflow', 'build a workflow', 'generate a workflow', 'make a workflow', 'I want to automate', 'automate X to Y', 'schedule a task', 'monitor X and send to Y'. This skill creates workflows from simple YAML plans with automatic validation and import."
Workflow Generator
Generate complete workflows from simple YAML plans - one command, zero errors.
⚠️ CRITICAL: Use API to Build Workflows
NEVER manually write workflow JSON! Create a YAML plan and use the API:
curl -X POST http://localhost:3123/api/workflows/build-from-plan \
-H "Content-Type: application/json" \
-d '{"planPath": "plans/my-workflow.yaml"}'
What the API does automatically (12-layer validation):
- ✅ Validates modules exist in registry
- ✅ Validates parameter names match signatures
- ✅ Detects unsupported features (rest parameters, function-as-string)
- ✅ Auto-wraps params/options functions
- ✅ Builds workflow JSON
- ✅ Validates schema structure
- ✅ Validates trigger configuration (cron schedule, chat inputVariable)
- ✅ Validates returnValue variable exists
- ✅ Analyzes credential usage
- ✅ Detects unused variables (dead code)
- ✅ Runs dry-run test with mocks
- ✅ Automatically imports to database
Result: If it builds, it's immediately available in the app. Zero runtime errors.
Process Overview
User Request → Ask Questions → Create YAML Plan → Call API → Done!
↑ ↑
Clarify requirements Automatic build & import via API
3 Simple Steps:
- Ask clarifying questions (AskUserQuestion tool)
- Create plans/my-workflow.yaml based on answers (use Write tool)
- Call API: POST http://localhost:3123/api/workflows/build-from-plan with {"planPath": "plans/my-workflow.yaml"}
Note: The API returns the workflow JSON and automatically imports it to the database.
STEP 1: Ask Clarifying Questions
ALWAYS start by asking questions using the AskUserQuestion tool.
Question Templates
Always ask at minimum:
- Trigger type - When workflow runs (manual/cron/webhook/chat)
- Output format - How to display results (json/table/text)
Additional questions based on workflow type:
- Social Media: Deduplication (Yes/No)
- AI Generation: AI Model (GPT-4o-mini/Claude/etc)
- Data Processing: Operations needed (filter/transform/aggregate)
Standard question template:
AskUserQuestion({
questions: [
{
question: "When should this workflow run?",
header: "Trigger",
multiSelect: false,
options: [
{ label: "Manual", description: "On-demand (click Run)" },
{ label: "Scheduled", description: "Automatic (cron)" },
{ label: "Webhook", description: "External HTTP" },
{ label: "Chat", description: "AI conversation" },
{ label: "Telegram Bot", description: "Telegram messages" },
{ label: "Discord Bot", description: "Discord messages" }
]
},
{
question: "How should results be displayed?",
header: "Output",
multiSelect: false,
options: [
{ label: "JSON", description: "Raw data format" },
{ label: "Table", description: "Structured columns" },
{ label: "List", description: "List of items" },
{ label: "Text", description: "Plain text" },
{ label: "Markdown", description: "Formatted text" }
]
}
]
})
STEP 2: Create Workflow Plan
Based on user answers, create a YAML plan file:
Plan Format
name: Workflow Name
description: Optional workflow description
trigger: manual | cron | webhook | telegram | discord | chat | chat-input
webhookSync: true | false # Optional, for webhook triggers - returns workflow output instead of {"queued": true}
webhookSecret: "secret-key" # Optional, for webhook triggers - enables HMAC signature verification
output: json | table | list | text | markdown | image | images | chart
outputColumns: [col1, col2] # Optional, for table/list output
category: category-name # Optional
tags: [tag1, tag2] # Optional
steps:
- module: category.module.function
id: unique-step-id
name: Human Readable Name (optional)
inputs:
param1: "{{variable}}"
param2: value
outputAs: variableName (optional)
Trigger Variables (CRITICAL)
Each trigger type provides specific variables accessible in workflows:
Webhook triggers provide trigger object with:
trigger.body- JSON request body (e.g.,trigger.body.email,trigger.body.numbers)trigger.headers- HTTP headers objecttrigger.query- Query parameters objecttrigger.method- HTTP method (POST, GET, etc.)trigger.url- Request URL path
Chat triggers provide:
trigger.messageortrigger.userInput- User's message texttrigger.conversationId- Conversation ID
Telegram/Discord triggers provide:
trigger.message- Message texttrigger.userId- User IDtrigger.chatId- Chat/channel ID
Manual/Cron triggers provide:
- No trigger data (start with hardcoded values or config)
IMPORTANT: Always use trigger.body.fieldName for webhook data, NOT webhookData.fieldName or input.fieldName!
User Answer → Plan Mapping
Trigger Types (choose one):
manual- On-demand execution (click Run button)cron- Scheduled execution (set frequency in UI after import)webhook- External HTTP trigger (no auth required)- Async mode (default): Returns
{"queued": true}immediately - Sync mode: Add
?sync=trueto URL or setsync: truein trigger config to return actual workflow output
- Async mode (default): Returns
telegram- Telegram bot message triggerdiscord- Discord bot message triggerchat- AI chat conversation triggerchat-input- Chat with structured input
Output Types (choose one):
json- Raw JSON datatable- Structured table with columnslist- List of itemstext- Plain text outputmarkdown- Formatted markdownimage- Single image displayimages- Multiple images gallerychart- Data visualization chart
Common mappings from user answers:
- "Manual" →
trigger: manual - "Scheduled" →
trigger: cron - "JSON" →
output: json - "Table" →
output: table
AI Model Answer (for AI workflows):
- "GPT-4o-mini" → Add step with
model: gpt-4o-mini, provider: openai - "Claude Haiku" → Add step with
model: claude-haiku-4-5-20251001, provider: anthropic
Deduplication Answer (for social workflows):
- "Yes" → Add storage steps (queryWhereIn, filter, insertRecord)
- "No" → Skip storage steps
Finding Modules
Search for modules you need:
npm run modules:search <keyword> -- --limit 5
Use the path from search results as the module in your plan.
Available Module Categories
Utilities (No API Keys Required):
utilities.math- 20+ operations (add, subtract, multiply, divide, max, min, round, floor, ceil, abs, power, sqrt, etc.)utilities.array-utils- 30+ operations (first, last, sort, filter, group, pluck, sum, average, unique, flatten, chunk, etc.)utilities.string-utils- 15+ operations (toSlug, camelCase, pascalCase, truncate, capitalize, stripHtml, isEmail, etc.)utilities.datetime- 20+ operations (now, format, parse, add/subtract days/hours, comparisons, start/end of day, etc.)utilities.json-transform- 15+ operations (parse, stringify, get, set, pick, omit, merge, flatten, unflatten, etc.)utilities.csv- 5+ operations (parse, stringify, csvToJson, jsonToCsv, etc.)utilities.xml- 10+ operations (parse, build, validate, extract, etc.)utilities.validation- 5+ operations (validateRequired, validateTypes, validateEmail, validateUrl, etc.)utilities.aggregation- 7+ operations (median, variance, stdDev, percentile, mode, etc.)utilities.filtering- 5+ operations (filterByCondition, findByCondition, containsAll, containsAny, etc.)utilities.batching- 5+ operations (chunk, createBatches, paginate, etc.)utilities.control-flow- 10+ operations (conditional, switchCase, ifElse, partitionByCondition, tryCatch, retry, sleep, etc.)utilities.javascript- 7+ operations (execute, filterArray, mapArray, reduceArray, evaluateExpression, etc.)utilities.http- 5+ operations (httpGet, httpPost, httpPut, httpDelete, httpRequest, etc.)
AI (Requires API Keys):
ai.ai-sdk- generateText, chat, streamGeneration, generateJSON, etc.
Data (Database Access):
data.drizzle-utils- queryWhereIn, insertRecord, updateRecord, deleteRecord, etc.
Social Media (Requires Platform Credentials):
social.twitter.*,social.reddit.*,social.linkedin.*, etc.
Use npm run modules:search <keyword> to find specific modules.
Example Plans
Simple Math Workflow:
name: Test Math Utilities
trigger: manual
output: json
steps:
- module: utilities.javascript.evaluateExpression
id: setup-data
inputs:
expression: "({numbers: [1, 2, 3, 4, 5]})"
outputAs: data
- module: utilities.math.max
id: calc-max
inputs:
numbers: "{{data.numbers}}"
outputAs: maximum
- module: utilities.array-utils.sum
id: calc-sum
inputs:
arr: "{{data.numbers}}"
AI Content Generation:
name: Generate Blog Post
trigger: manual
output: text
steps:
- module: ai.ai-sdk.generateText
id: generate-content
inputs:
prompt: "Write a blog post about {{topic}}"
model: gpt-4o-mini
provider: openai
Webhook with Synchronous Response:
name: Data Validation API
description: Webhook that validates and processes data, returns results synchronously
trigger: webhook
webhookSync: true # Enable sync mode - returns output instead of {"queued": true}
webhookSecret: "my-secret" # Optional - enables HMAC signature verification
output: json
returnValue: "{{validationResult}}"
steps:
# 1. Validate incoming data
# CRITICAL: Use trigger.body to access webhook POST data
- module: utilities.javascript.execute
id: validate-data
inputs:
code: |
const errors = [];
if (!trigger.body.email || !trigger.body.email.includes('@')) {
errors.push('Invalid email');
}
if (!trigger.body.age || trigger.body.age < 18) {
errors.push('Age must be 18+');
}
return {
valid: errors.length === 0,
errors: errors,
data: trigger.body
};
outputAs: validation
# 2. Process if valid, return error if not
- module: utilities.javascript.execute
id: process-result
inputs:
code: |
if (!validation.valid) {
return {
status: 'error',
errors: validation.errors
};
}
return {
status: 'success',
message: 'Data validated successfully',
processedAt: new Date().toISOString()
};
context:
validation: "{{validation}}"
outputAs: validationResult
# Usage:
# POST /api/workflows/{id}/webhook?sync=true
# Body: {"email": "test@example.com", "age": 25}
# Response: {"status": "success", "message": "...", "processedAt": "..."}
Complex Data Pipeline (Multi-step transformation):
name: Data Processing Pipeline
trigger: manual
output: table
outputColumns: [id, name, category, score]
steps:
# 1. Generate test data
- module: utilities.javascript.evaluateExpression
id: raw-data
inputs:
expression: "([{id: 1, name: 'Item A', value: 100, type: 'premium'}, {id: 2, name: 'Item B', value: 50, type: 'basic'}])"
outputAs: data
# 2. Filter high-value items
- module: utilities.filtering.filterArrayByCondition
id: filter-items
inputs:
items: "{{data}}"
field: value
operator: ">"
value: 75
outputAs: filtered
# 3. Transform data structure
- module: utilities.javascript.execute
id: transform
inputs:
code: "return items.map(item => ({id: item.id, name: item.name, category: item.type, score: item.value / 10}))"
context:
items: "{{filtered}}"
outputAs: transformed
# 4. Sort by score
- module: utilities.array-utils.sortBy
id: sort-results
inputs:
arr: "{{transformed}}"
key: score
order: desc
outputAs: finalResults
Social Media with Deduplication:
name: Reply to Tweets
trigger: cron
output: table
steps:
- module: social.twitter.searchTweets
id: search-tweets
inputs:
query: "AI automation"
maxResults: 10
outputAs: tweets
- module: utilities.array-utils.pluck
id: extract-ids
inputs:
arr: "{{tweets}}"
key: id
outputAs: tweetIds
- module: data.drizzle-utils.queryWhereIn
id: check-replied
inputs:
workflowId: "{{workflowId}}"
tableName: replied_tweets
column: tweet_id
values: "{{tweetIds}}"
outputAs: repliedIds
- module: utilities.filtering.filterArrayByCondition
id: filter-new
inputs:
items: "{{tweets}}"
field: id
operator: not in
value: "{{repliedIds}}"
outputAs: newTweets
- module: ai.ai-sdk.generateText
id: generate-reply
inputs:
prompt: "Write a reply to: {{newTweets[0].text}}"
model: gpt-4o-mini
provider: openai
outputAs: reply
- module: social.twitter.replyToTweet
id: post-reply
inputs:
tweetId: "{{newTweets[0].id}}"
text: "{{reply.content}}"
- module: data.drizzle-utils.insertRecord
id: store-replied
inputs:
workflowId: "{{workflowId}}"
tableName: replied_tweets
data:
tweet_id: "{{newTweets[0].id}}"
ttl: 2592000
STEP 3: Build Workflow
npm run workflow:build plans/workflow-plan.yaml
Validation Output Example:
🔍 Validating 5 steps...
✅ Step 1 ("setup-data") validated
✅ Step 2 ("calc-max") validated
... (all steps)
✅ All steps validated successfully!
🔍 Validating trigger configuration...
✅ Cron schedule valid: "0 * * * *"
🔍 Validating returnValue...
✅ ReturnValue variable "result" is produced by step: final-step
🔍 Analyzing credential usage...
📋 Credentials used: openai_api_key
⚠️ Undocumented credentials: openai_api_key
🔍 Analyzing data flow...
⚠️ Unused variables: tempVar
🧪 Running dry-run test...
Step 1/5: setup-data ✅
Step 2/5: calc-max ✅
... (all steps execute with mocks)
✅ Dry-run completed successfully!
✅ Workflow imported successfully!
🎉 View at: http://localhost:3123/dashboard/workflows
If errors exist:
❌ Step "calc-max": Module "utilities.math.max" uses rest parameters (...) which are not supported
💡 Use utilities.array-utils.max instead
❌ ReturnValue references "{{nonExistent}}" but no step produces it
❌ Dry-run failed: Step "step2" - Unresolved variable {{undefinedVar}}
Workflow NOT imported - fix errors first
Critical Rules
- ALWAYS ask questions first - Use AskUserQuestion tool
- ALWAYS use workflow:build - Never manually write JSON
- Search for modules - Use
npm run modules:searchto find module paths - Create YAML plan - Simple, readable format
- Auto-wrapping works - Don't wrap params/options manually, script does it
- Expect zero errors - Plan builder validates everything
Common Patterns
Pattern: Social Media Deduplication
Steps needed:
- Search/fetch items
- Extract IDs (pluck)
- Check storage (queryWhereIn)
- Filter new items
- Process new items
- Store IDs (insertRecord with TTL)
Pattern: AI Content Generation
Steps needed:
- Prepare input data
- Generate with ai.ai-sdk.generateText
- Extract content (.content property)
- Return or post
Advanced Features
Custom JavaScript Logic
For complex operations that need functions (filter, map, transform), use JavaScript modules:
Filter array with custom logic:
- module: utilities.javascript.filterArray
id: filter-items
inputs:
items: "{{data}}"
code: "return item.score > 80" # Custom condition
Map/transform array:
- module: utilities.javascript.mapArray
id: transform-items
inputs:
items: "{{data}}"
code: "return { id: item.id, value: item.value * 2 }"
Reduce array:
- module: utilities.javascript.reduceArray
id: sum-values
inputs:
items: "{{data}}"
initialValue: 0
code: "return accumulator + item.value"
Any custom logic:
- module: utilities.javascript.execute
id: custom-logic
inputs:
code: "return data.filter(x => x > 5).map(x => x * 2)"
context:
data: "{{numbers}}"
ReturnValue (Optional)
The builder auto-sets returnValue to the last step's outputAs:
steps:
- module: utilities.math.add
id: final-calc
outputAs: result # Auto-set as returnValue
Custom returnValue:
returnValue: "{{customVariable}}" # Override auto-detection
steps:
# ...
Trigger Configuration
Cron triggers auto-get schedule: "0 * * * *" (hourly)
Chat triggers auto-get inputVariable: "userInput"
Customize in UI after import.
Common Issues & Solutions
❌ Rest Parameters (Spread)
Problem: Some modules use ...param (rest parameters) which don't work in workflows
Example: utilities.math.max(...numbers) expects individual arguments
Solution: Use array-utils versions instead
# ❌ Wrong - uses rest parameters:
- module: utilities.math.max
inputs:
numbers: [1, 2, 3] # Won't work!
# ✅ Correct - takes array:
- module: utilities.array-utils.max
inputs:
arr: [1, 2, 3]
Other affected modules: math.min, array-utils.intersection, array-utils.union, json-transform.deepMerge, control-flow.coalesce
❌ JavaScript Context
Problem: filterArray/mapArray only provide {item, index, items}, no custom context
Solution: Use javascript.execute for custom context
# ❌ Wrong - context not supported:
- module: utilities.javascript.filterArray
inputs:
items: "{{data}}"
code: "return item > threshold" # threshold undefined!
# ✅ Correct - use execute with context:
- module: utilities.javascript.execute
inputs:
code: "return data.filter(x => x > threshold)"
context:
data: "{{data}}"
threshold: 50
❌ Table Output Structure
Problem: Table output needs array of objects, not complex nested object
Solution: Point returnValue to the array
# ❌ Wrong:
returnValue: "{{complexObject}}" # Returns {posts: [...], meta: {...}}
# ✅ Correct:
returnValue: "{{complexObject.posts}}" # Returns just the array
Modules That Use Wrappers (Auto-Wrapped)
Options wrapper (inputs wrapped in options: {...}):
- All
ai.ai-sdk.*functions (generateText, chat, etc.) - All
utilities.javascript.*functions (execute, filterArray, mapArray, etc.)
Params wrapper (inputs wrapped in params: {...}):
- All
data.drizzle-utils.*functions (queryWhereIn, insertRecord, etc.)
Direct parameters (no wrapper needed):
- All
utilities.math.*functions - All
utilities.array-utils.*functions - All
utilities.string-utils.*functions - All
utilities.datetime.*functions - All
utilities.json-transform.*functions - All
utilities.csv.*,xml.*,validation.*, etc.
The builder detects wrapper requirements automatically - just write inputs directly in YAML!
Tips
- Module search:
npm run modules:search <keyword>to find module paths - Rest parameters: Use
array-utils.*versions, notmath.*for max/min - Wrapper modules: AI SDK, drizzle-utils, JavaScript modules - automatically wrapped
- JavaScript code: Use
javascript.executefor custom context,filterArray/mapArrayfor simple operations - Storage: Always use
{{workflowId}}in storage operations - Variable references: Use
{{variableName}}or{{variableName.property}} - ReturnValue: Auto-set from last
outputAs, or specify custom - Credentials: Add to
metadata.requiresCredentialsfor documentation - Plans directory: Always save plans in
plans/directory - YAML types: Use
{}for objects,[]for arrays (not"{}"or"[]"strings)
Full Example Session
User: "Create a workflow to calculate statistics on numbers"
LLM Action 1: Ask questions
AskUserQuestion({
questions: [
{ question: "When should this run?", header: "Trigger", ... },
{ question: "Output format?", header: "Output", ... }
]
})
User Answers: "Manual", "JSON"
LLM Action 2: Create plan
# workflow-plan.yaml
name: Calculate Number Statistics
trigger: manual
output: json
steps:
- module: utilities.javascript.evaluateExpression
id: setup-numbers
inputs:
expression: "([1, 2, 3, 4, 5, 10, 20, 30])"
outputAs: numbers
- module: utilities.math.max
id: calc-max
inputs:
numbers: "{{numbers}}"
outputAs: maximum
- module: utilities.math.min
id: calc-min
inputs:
numbers: "{{numbers}}"
outputAs: minimum
- module: utilities.array-utils.average
id: calc-avg
inputs:
arr: "{{numbers}}"
outputAs: average
- module: utilities.aggregation.median
id: calc-median
inputs:
numbers: "{{numbers}}"
outputAs: median
LLM Action 3: Build workflow
npm run workflow:build plans/workflow-plan.yaml
Output:
✅ All steps validated
✅ Workflow created and imported!
View at: http://localhost:3123/dashboard/workflows
LLM Response: "✅ Workflow 'Calculate Number Statistics' created with 5 steps! All modules validated. Ready to use at http://localhost:3123/dashboard/workflows"
Advantages
✅ Simple - One YAML file, one command ✅ Safe - All validation automatic ✅ Fast - Zero error iterations ✅ Clear - YAML is readable ✅ Smart - Auto-wraps params/options
Use workflow:build for all workflow generation!