Workflow Migrate — Automation Migration Tool
Why This Exists
N8N/Zapier/Make workflows break silently, can't be version-controlled, and cost $50-500/month in SaaS fees. This skill rewrites them as standalone scripts that run forever with zero subscription cost. Each migration is a $500-5000 billable deliverable.
Trigger
Use when: "migrate this workflow", "convert N8N to Python", "rewrite my Zapier", "turn this automation into a script", "get off N8N"
Invoked as: /workflow-migrate [workflow description or paste]
Process
Step 1: Parse the Workflow Input
From $ARGUMENTS:
- - If it's a description ("when a form is submitted, send email and update Airtable"): parse directly
- If it's N8N JSON: read and extract nodes/connections
- If it's Zapier steps: parse the trigger + action chain
- If it's Make (formerly Integromat) scenario: parse modules
Extract and document:
Triggers:
- - Webhook (POST endpoint)
- Cron/schedule (every X minutes/hours)
- Form submission
- Email received
- File drop (S3, Google Drive, local folder)
- DB row created/updated
Actions:
- - HTTP/API calls (list each endpoint, method, payload)
- Database reads/writes
- Email sends
- File operations
- Conditional logic (if/else branches)
- Loops over arrays
- Data transformations
Data flow:
- - Input payload fields used
- Intermediate computed values
- Output destinations
If the workflow description is too vague, ask 2-3 targeted questions before proceeding:
- - "What triggers it — webhook, schedule, or something else?"
- "Which API endpoints does it call and with what data?"
- "What counts as success? What should happen on failure?"
Step 2: Choose Language
Default to Python unless:
- - The workflow is heavily async/event-driven (prefer Node.js)
- Existing codebase is Node.js
- Kevin explicitly requests Node
Python stack: requests, schedule, logging, tenacity (retry), python-dotenv
Node.js stack: axios, node-cron, winston, async-retry, dotenv
Step 3: Write the Script
Generate a complete, runnable script. Required elements:
Python Template Structure:
CODEBLOCK0
Node.js Template Structure:
CODEBLOCK1
For each action in the workflow, write a dedicated function:
- - One function per logical action (e.g.,
fetch_leads(), send_notification(), update_database()) - Functions are composable and testable in isolation
- Every function logs what it's doing: start, result, any errors
Main orchestrator:
CODEBLOCK2
Self-healing patterns to include:
- - Alert on failure (Telegram message via Kevin's bot token if available, else log to file)
- Idempotency: skip already-processed records (use a state file or DB flag)
- Dead-letter queue: failed items saved to
failed_[date].json for manual review - Heartbeat: log "still alive" every N runs for scheduled workflows
Step 4: Generate .env.example
CODEBLOCK3
Step 5: Generate requirements.txt or package.json
Python:
CODEBLOCK4
Node.js:
{
"dependencies": {
"axios": "^1.6.0",
"async-retry": "^1.3.3",
"node-cron": "^3.0.3",
"winston": "^3.11.0",
"dotenv": "^16.3.1"
}
}
Step 6: If the Workflow is Recurring — Generate a SKILL.md
If the workflow runs on a schedule or will be reused:
- - Generate a
SKILL.md in the same output directory - Name it after the workflow function
- Description: "Run [workflow name] — [what it does in 10 words]"
- Instructions: path to script, how to run it, what env vars are needed
Step 7: Save Outputs and Print Migration Summary
Output location: Ask Kevin where to save, default to INLINECODE17
Create:
- -
workflow_[name].py (or .js) - INLINECODE20
- INLINECODE21 (or
package.json) - INLINECODE23 (if recurring)
- INLINECODE24 (quick usage guide — 20 lines max)
Print migration summary:
Migration complete.
Original: [N8N/Zapier/Make] workflow — [X] nodes/steps
Output: ./workflow_[name]/workflow_[name].py
What changed:
- [X] N8N nodes → [Y] lines of Python
- Added: retry with exponential backoff (3 attempts, 2s-30s)
- Added: rotating log file (monthly)
- Added: dry-run mode (--dry-run flag)
- Added: failure alerts via [Telegram/log]
- Removed: $XX/month SaaS subscription
To run:
cd workflow_[name]/
cp .env.example .env # fill in your keys
pip install -r requirements.txt
python workflow_[name].py --dry-run # test first
python workflow_[name].py # run for real
Error Handling
- - Ambiguous workflow: ask 2-3 targeted questions, don't guess at API endpoints
- Proprietary N8N nodes (e.g., OpenAI node): rewrite using the raw API — include API docs link in comments
- Very complex workflow (>15 nodes): break into multiple scripts with a coordinator, document the dependency order
- No credentials visible: generate .env.example with clear placeholders, add comments explaining where each key comes from
- Webhook trigger: generate a Flask/Express endpoint stub that receives the webhook and calls the workflow function
Notes
- - Always test with
--dry-run before running for real - State file for idempotency:
./state/processed_ids.json — create state/ dir if it doesn't exist - For scheduled runs, prefer
cron / schedule over external task runners (fewer dependencies)
工作流迁移 — 自动化迁移工具
为什么存在
N8N/Zapier/Make 工作流会静默失效,无法进行版本控制,且每月需支付50-500美元的SaaS费用。本技能将其重写为独立脚本,可永久运行且无需任何订阅费用。每次迁移都是价值500-5000美元的可交付成果。
触发条件
使用场景:迁移此工作流、将N8N转换为Python、重写我的Zapier、将此自动化转为脚本、脱离N8N
调用方式:/workflow-migrate [工作流描述或粘贴内容]
流程
步骤1:解析工作流输入
从 $ARGUMENTS:
- - 如果是描述(当表单提交时,发送邮件并更新Airtable):直接解析
- 如果是N8N JSON:读取并提取节点/连接
- 如果是Zapier步骤:解析触发器+操作链
- 如果是Make(原Integromat)场景:解析模块
提取并记录:
触发器:
- - Webhook(POST端点)
- Cron/定时任务(每X分钟/小时)
- 表单提交
- 收到邮件
- 文件投放(S3、Google Drive、本地文件夹)
- 数据库行创建/更新
操作:
- - HTTP/API调用(列出每个端点、方法、负载)
- 数据库读写
- 邮件发送
- 文件操作
- 条件逻辑(if/else分支)
- 数组循环
- 数据转换
数据流:
如果工作流描述过于模糊,在继续之前提出2-3个针对性问题:
- - 触发方式是什么——webhook、定时任务还是其他?
- 它调用哪些API端点,使用什么数据?
- 什么算成功?失败时应该怎么做?
步骤2:选择语言
默认使用Python,除非:
- - 工作流高度异步/事件驱动(优先选择Node.js)
- 现有代码库是Node.js
- Kevin明确要求使用Node
Python技术栈:requests、schedule、logging、tenacity(重试)、python-dotenv
Node.js技术栈:axios、node-cron、winston、async-retry、dotenv
步骤3:编写脚本
生成完整可运行的脚本。必需元素:
Python模板结构:
python
#!/usr/bin/env python3
[工作流名称] — 从[N8N/Zapier/Make]迁移
原始:[工作流功能的简要描述]
迁移日期:[日期]
用法:
python workflow_[名称].py # 运行一次
python workflow_[名称].py --schedule # 按计划运行
python workflow_[名称].py --dry-run # 测试,无副作用
import os
import sys
import logging
import time
import argparse
from datetime import datetime
from typing import Optional, Dict, Any
import requests
from tenacity import retry, stopafterattempt, waitexponential, retryifexceptiontype
from dotenv import load_dotenv
load_dotenv()
─── 日志 ───────────────────────────────────────────────────────────────────
logging.basicConfig(
level=logging.INFO,
format=%(asctime)s [%(levelname)s] %(message)s,
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler(flogs/workflow_{datetime.now().strftime(%Y%m)}.log),
]
)
log = logging.getLogger(
name)
─── 配置 ────────────────────────────────────────────────────────────────────
API
KEY = os.getenv(APIKEY) # 来自 .env
WEBHOOK
URL = os.getenv(WEBHOOKURL) # 目标地址
DRY_RUN = False
─── 重试装饰器 ───────────────────────────────────────────────────────────
@retry(
stop=stop
afterattempt(3),
wait=wait_exponential(multiplier=1, min=2, max=30),
retry=retry
ifexception_type((requests.RequestException, ConnectionError)),
before
sleep=lambda rs: log.warning(f正在重试(第{rs.attemptnumber}次尝试)...),
reraise=True
)
def api_call(method: str, url: str,
kwargs) -> Dict[str, Any]:
进行HTTP调用,自动重试并指数退避。
if DRY_RUN:
log.info(f[模拟运行] {method.upper()} {url} 负载={kwargs.get(json, {})})
return {dry_run: True}
resp = requests.request(method, url, timeout=30,
kwargs)
resp.raise
forstatus()
return resp.json()
Node.js模板结构:
javascript
#!/usr/bin/env node
/
* [工作流名称] — 从[N8N/Zapier/Make]迁移
* 原始:[简要描述]
* 迁移日期:[日期]
*
* 用法:
* node workflow_[名称].js # 运行一次
* node workflow_[名称].js --schedule # 按计划运行
* node workflow_[名称].js --dry-run # 测试,无副作用
*/
require(dotenv).config();
const axios = require(axios);
const retry = require(async-retry);
const cron = require(node-cron);
const winston = require(winston);
const fs = require(fs);
// ─── 日志器 ────────────────────────────────────────────────────────────────────
const logger = winston.createLogger({
level: info,
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
transports: [
new winston.transports.Console({ format: winston.format.simple() }),
new winston.transports.File({ filename: logs/workflow_${new Date().toISOString().slice(0,7)}.log }),
],
});
// ─── 配置 ────────────────────────────────────────────────────────────────────
const APIKEY = process.env.APIKEY;
const DRY_RUN = process.argv.includes(--dry-run);
// ─── 重试包装器 ─────────────────────────────────────────────────────────────
async function apiCall(method, url, data = {}, headers = {}) {
return retry(async (bail, attempt) => {
if (DRY_RUN) {
logger.info([模拟运行] ${method.toUpperCase()} ${url}, { data });
return { dry_run: true };
}
try {
const resp = await axios({ method, url, data, headers, timeout: 30000 });
return resp.data;
} catch (err) {
if (err.response && err.response.status < 500) bail(err); // 4xx = 不重试
logger.warn(正在重试第${attempt}次尝试..., { error: err.message });
throw err;
}
}, { retries: 3, minTimeout: 2000, maxTimeout: 30000, factor: 2 });
}
对于工作流中的每个操作,编写专用函数:
- - 每个逻辑操作一个函数(例如 fetchleads()、sendnotification()、update_database())
- 函数可组合且可独立测试
- 每个函数记录其操作:开始、结果、任何错误
主编排器:
python
def run(dry_run: bool = False):
global DRY_RUN
DRYRUN = dryrun
log.info(=== 工作流开始 ===)
try:
# 步骤1:[操作名称]
data = fetchsourcedata()
log.info(f获取了{len(data)}条记录)
# 步骤2:[操作名称]
for item in data:
result = process_item(item)
if result:
sendtodestination(result)
log.info(=== 工作流成功完成 ===)
except Exception as e:
log.error(f工作流失败:{e}, exc_info=True)
send_alert(f工作流[名称]失败:{e}) # 自愈警报
raise
需包含的自愈模式:
- - 失败时告警(如有可用,通过Kevin的机器人令牌发送Telegram消息,否则记录到文件)
- 幂等性:跳过已处理的记录(使用状态文件或数据库标志)
- 死信队列:失败项保存到 failed_[日期].json 供人工审查
- 心跳:对于定时工作流,每N次运行记录仍在运行
步骤4:生成 .env.example
bash
[工作流名称] — 环境变量
复制到 .env 并填写值
API_KEY=在此填写您的API密钥
WEBHOOK_URL=https://...
DATABASE_URL=...
可选:失败时发送Telegram告警
TELEGRAM
BOTTOKEN=
TELEGRAM