把 $7.88/月的 n8n on Zeabur 換成 178 行的 OpenClaw 本地腳本
把一個家庭行程提醒從託管的 n8n 搬到本地 OpenClaw cron — 順手修了一個還沒爆的 bug。
背景
過去一年,我在 Zeabur 上養著一個 n8n workflow:family_schedule_alert。每天早上 6:00 抓 Google Calendar 當日的事件,篩出 summary 含有家人名字的,整理成一段易讀的內容,推到一個照護群成員都在的 LINE 群組。
當初選 n8n 是阻力最小的路:視覺化編輯器、內建 Google Calendar OAuth、Schedule trigger、HTTP node 打 LINE Push。十個 node、收工。Zeabur 跑容器、每月帳單寄來,6 點機器人準時報到。月費高峰是 USD $7.88。
觸發點
2026 年中,我把 OpenClaw 跑在家裡的機器上已經一段時間 —— 長到能放心交付真正在做的工作,也長到開始意識到:Zeabur 上那個僅存的 n8n workflow 做的事,OpenClaw 本地早就做得到了。
當替代品就在同一張桌子上的時候,一個託管 workflow 撐不起每月 $7.88 的 line item。搬。
Case study:家人每日行程提醒
原本的 n8n graph 有十個 node:
Schedule Trigger— 06:00 觸發Google Calendar— 抓$today到$today.plus(1, "day")之間的所有事件If_keyword_in_summary— 用summary contains <家人名>篩daily_schedule_list— Set node,投影欄位if_location_undefined+add_location+Merge— 沒有地點就補「未標示」Aggregate— 把所有事件收成一個 payloadCode— Python code node,格式化成一段 LINE 友善的文字line_push— HTTP POST 打 LINE Push API
視覺化 graph 截圖很漂亮,但每個 node 實際上都只是包了兩三行真實邏輯的薄殼,外加散在六七個 JSON 設定面板裡的 n8n 專屬 expression 語法。真正的 程式碼住在 Python Code node 裡 —— 視覺抽象到那邊就放棄了,剩下你還是要手寫字串模板拼 Python。
OpenClaw 重寫版
一個檔案、178 行、純 Python stdlib —— 沒有 n8n、沒有 node_modules、沒有第三方套件。實際在做的三件事是 token refresh、calendar fetch、LINE push:
def get_access_token():
with open(TOKEN_PATH) as f:
creds = json.load(f)
body = urllib.parse.urlencode({
"client_id": creds["client_id"],
"client_secret": creds["client_secret"],
"refresh_token": creds["refresh_token"],
"grant_type": "refresh_token",
}).encode("utf-8")
req = urllib.request.Request(
creds["token_uri"], data=body,
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode("utf-8"))["access_token"]
def fetch_today_events(access_token):
now_tw = dt.datetime.now(TZ)
today_start = now_tw.replace(hour=0, minute=0, second=0, microsecond=0)
today_end = today_start + dt.timedelta(days=1)
params = {
"timeMin": today_start.isoformat(),
"timeMax": today_end.isoformat(),
"q": KEYWORD, # server-side prefilter
"singleEvents": "true",
"orderBy": "startTime",
}
url = (f"https://www.googleapis.com/calendar/v3/calendars/"
f"{urllib.parse.quote(CALENDAR_ID)}/events"
f"?{urllib.parse.urlencode(params)}")
req = urllib.request.Request(
url, headers={"Authorization": f"Bearer {access_token}"}
)
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode("utf-8")).get("items", [])
~/.openclaw/scripts/family_schedule_alert.py — 關鍵段落
OAuth 拆成兩支腳本。setup_google_oauth.py 跑一次:開瀏覽器、用本機 loopback 接 auth code、換 refresh token、把結果寫到 ~/.openclaw/credentials/google_oauth_token.json (權限 0600)。之後每天的腳本只用 refresh token (不會過期),每次跑都換一個新的 access token。
順手修掉的潛在 bug
原本 n8n Set node 是這樣投影時間欄位的:
Google Calendar 對全日事件回傳的是 start.date,不是 start.dateTime。n8n 版本在第一次有人把家人的全日事件加進行事曆的當下就會炸。它從來沒炸 —— 是因為實務上還沒人這樣加而已。重寫的時候順手就修了,這種「閱讀時發現、不是 06:01 在 debug 時發現」的修法是最好的修法。
OpenClaw cron + agentTurn
OpenClaw 的 cron 不是 crontab 的薄殼。每個 job 的 payload 是 agentTurn —— 排程到時喚醒一個 agent、丟一段 prompt 給它、讓 agent 去做、最後把 agent 的回覆當成這次 run 的 summary。這個 job 的 prompt 故意寫得很直白:
{
"id": "86ce0520-afc8-47c7-a47f-7681fc6cba6f",
"name": "每日行程提醒",
"schedule": { "kind": "cron", "expr": "0 6 * * *", "tz": "Asia/Taipei" },
"agentId": "main",
"payload": {
"kind": "agentTurn",
"message": "Run this bash command and report nothing else: /usr/bin/python3 /Users/fish/.openclaw/scripts/family_schedule_alert.py",
"timeoutSeconds": 60
},
"delivery": { "mode": "announce", "channel": "telegram", "to": "971742907" }
}
cron job 86ce0520-…-6cba6f payload
用 LLM agent 去執行一行 bash 確實有點殺雞用牛刀 —— 一次 fire 用掉 290 input / 145 output token 也不是免費的。但同一個排程框架除了跑這種笨 cron job,也跑那種 payload 寫著 「整理今天 Threads 的留言、有需要我注意的就跟我說」 的 reasoning job。一個 cron 介面、一套 delivery、一個歷史檢視器。讓 agent 去執行簡單情境的成本,就是不必另外跑第二套排程系統的代價。
這台機器上 OpenClaw 還在做什麼
搬一個 workflow 本身不真的值 $7.88;價值是把搬遷成本攤平在同一台機器上已經在跑的其他事情上。同一個 OpenClaw cron registry 還在跑:
- 排程化的內容生產線 —— 數條主題垂直的多段式流程,每條各有自己的節奏。
- 社群平台維運 —— 排程化的內容整理、報告,與免人工的憑證續期。
- 市場 / 研究 —— 各自節奏的市場掃描與週期性研究報告。
- 自我照護基礎設施 —— gateway 每 30 分鐘自我修復、記憶每小時備份、凌晨 3 點 memory-promote、每月 Daily Log 歸檔。
- 以及,現在多了一個家庭行程提醒。
全部跑在同一台機器、同一個排程系統、運維狀態統一推到一個 Telegram chat。再塞一個同樣形狀的本地 job 來取代 n8n workflow,邊際成本基本上是 0。
心得
$7.88/月 是那種不會觸發警報的金額 —— 三杯咖啡、一個串流訂閱。但紀律就是要持續問「這筆錢買的是什麼、我還需要它嗎?」一旦答案是「一個我下午就能重寫的 workflow」,決定其實已經做完了。
十個 n8n node 在 canvas 上看起來架構感很強,但程式碼其實沒多少。一旦出現 Code node —— 那個放諸四海皆通的「我放棄視覺抽象」訊號 —— workflow 真正的邏輯都已經搬出 graph 了。Graph 是包裝;包裝才是你在付錢的東西。
一個 cron job 撐不起一套排程系統,二十個就可以。最好的遷移時機是:你已經為了其他理由付過框架成本,然後手上還剩一個僅靠單一 workflow 撐著的託管工具。