大家好,我是烧麦
最近全自动化 Agent 非常火热,前段时间不也是有 Clawbot 的爆发。综合来说,这是一个结合了这之前的 Agent Skills 的理念的一个东西,如果一定要下一个概念,就是 Clawbot 是 Agent Skill 的上司,而我们是 Claw bot 的老板,只需要在聊天工具中给他发送一条消息,他就能控制我们的电脑来开展一些任务行为。
现在不少人认为这个 Agent 是一个可以 7*24 小时不下班的全天候员工,甚至说是“现实版的贾维斯”

其官方地址如下,别搜索错了,现在网上已经有抄袭的相似东西出来了,因为这个 Clawbot 本身就是开源的,很容易被二改套壳。
Moltbot — Personal AI Assistant
Moltbot — The AI that actually does things. Your personal assistant on any platform.
因为这东西被传的神乎其神,我也挺好奇,但我的实践过程磕碰挺多,各种报错,让我头皮发麻,折腾了快 10 个小时才终于在 VPS 中部署好。在接下来的流程中,我会将我遇见的错误,我的解决方案全部都披露出来供大家参考,免得重复造轮子了。
你需要先注意
这里我们先要清楚一个事情,就是 Clawbot 他只是一个轻量化的网关,与现实设备的高级与否没有任何的关系,以下是 Clawbot 官方发的推特,至于说大家为什么疯抢 mac mini,这里面有三个原因,所以我们这种玩家按时还不用心急,AI 的进化速度太快了,等等永远不迟:
- 环境隔离,因为 clawbot 属于索要权限特别高的 AI,所以你也不想某天起来,看见 AI 以你的口吻和署名给别人乱发消息和邮件,又或者说不经过你的同意乱折腾你电脑上的文件或者你的各种账号吧
- 24 小时待机,Mac mini 是一个非常棒的待机工具,能耗小,没声音,用来当一个永不关机的员工是非常好的事情,且也起到了环境隔离的作用,让 Clawbot 只能在 Mac mini 这台设备中操作东西,给它一个全新的独立的环境工作
- 省钱,Clawbot 是支持本地的大模型部署,这也是用户购买 mac mini 的原因之一,如果全程使用Claude Opus顶级模型,那一个月的 API 费用估计得上 500 刀应该是很容易的

但整体而言,你不用购买Mac mini ,就简单搞一台 VPS 服务器就行。那接下来我们就进入实操环节。
实操详细配置
强烈建议刚入坑的新手,没接触过的,先上 VPS 服务器试一试,流程跑通了,再直接在本地部署,不然直接上本地,到时候流程不对,如果出了各种错误,就很难回头了,我也不知道如何清楚本地残留的代码,总之就是比较麻烦。而 VPS 你直接删除新开一个就行了,比较方便,适合练手
Mac一键安装
我个人是使用的Mac 电脑,为了安全期间,我是使用的 VPS 部署,当然直接在mac 上部署更加简单,如果你是Mac ,直接搜索“终端”,打开终端后输入以下代码(官方提供),一键安装:
curl -fsSL <https://molt.bot/install.sh> | bash
使用 VPS 服务器
目前有两种方式,一种是你直接上腾讯云(国内版本)直接部署,目前提供一键部署,但缺点是只能用国内的大模型,关于阉割这件事,实属正常,参考这篇文章:
玩转Lighthouse|Moltbot(原Clawdbot)一键秒级部署指南-腾讯云开发者社区-腾讯云
ClawdBot是一款爆红的AI生活助理工具,通过WhatsApp与ClaudeCode无缝协作,支持本地电脑或云服务器部署。文章详解如何在腾讯云Lighthouse上快速部署ClawdBot,推荐选择海外地域服务器,提供2C2GB以上配置方案,实现7*24小时稳定运行。
另外一种是购买海外的大模型,这就是完全版,我目前是采用的这种方案。
这里话又说回来,既然我们是干“一人公司”的,那省钱肯定必须的,所以经过大量攻略,我选择了亚马逊的云服务器,AWS,他会给新注册的用户提供100 美元的免费额度,如图:

云计算服务 — Amazon Web Services(AWS)
Amazon Web Services 提供的云计算服务可靠、可扩展且费用合理。免费加入,只需按使用量付费。
真香啊,大慈善家。
详细步骤
当我们注册好这个服务器账号后,那我们直接开始配置 Clawbot,跟着下图操作即可:
这里有几个注意事项:
- 选择 Ubuntu 这个环境,其他的不行,至于AMI 这里选择什么,无所谓的,我保持原本的不动
- 关于实例类型,往大了选,免费的最大的是8G,真香。

- 服务器一定要链接密钥对,否则后续在本地的终端无法链接
选择完基本配置之后,点击右侧的启动实例,连接实例,这样即可进入服务器终端,然后输入以下的一键启动代码等待安装即可:
curl -fsSL <https://molt.bot/install.sh> | bash

关于安装过程中Clawbot 的选择问题
如下图配置参考(上下左右选择,空格为选中,Enter 键为确认):
- 选择Yes
- Quickstart(快速模式),
- 因为我常用Gemini,所以模型我选择的为Google ( 关于 AI 的 API,各位可以去你对应的 AI 工具后台寻找,然后填入,或者使用第三方的中转 API )
- Default model(默认模型)选择 Keep Current
- Enable hooks (选择功能)全选
Clawbot 不支持在 LaunchAgent 中设置 中转 API(无效!)。必须通过配置文件来修改, 这个我们在接入飞书的配置流程走完,再来详细讲一讲,先带大家把整个流程过一遍:


然后一直往下可以了,这个后面就是网关(Gateway)的安装与配置,直接选择 Yes ,然后稍等几分钟即可,他会自动安装完毕。
疑难点
💡
直接在本地安装的可以忽略这个疑难点,直接到接入飞书流程
因为我们是在 VPS 上安装的 Clawbot,所以它里面给出来的让你访问的本地链接是打不开的,如下:
<http://localhost:18789/?token=你的Token>
当你在浏览器输入这个链接,你的浏览器就会尝试连接你自己的这台电脑的 18789 端口,但实际是那里一片空白,什么都没有,所以就会报错

所以,我们需要在本地建立 SSH 隧道,也就是通过 SSH 把远程服务器的端口映射到本地,按照以下步骤操作:
- 打开你本地电脑的终端(Windows 用 PowerShell 或 CMD,Mac/Linux 用 Terminal)。
- 输入并运行以下命令:
ssh -i "/这里的路径/你的密钥文件名.pem" -N -L 18789:127.0.0.1:18789 ubuntu@你的公网IP
⚠️
如果你的服务器登录用户名不是 ubuntu,请修改它
操作细节提示:
- 怎么快速输入路径? 你可以先输入
ssh -i (后面带个空格),然后直接把你的 .pem 密钥文件从文件夹里拖进终端窗口,路径会自动生成。 - 成功标志: 运行后,如果终端没有任何输出(只是光标在下一行闪烁),那就说明隧道建立成功了!
- 不要关闭: 保持这个终端窗口开着。
- 在浏览器访问:
<http://localhost:18789/?token=你的Token>
关于权限问题
如果在之前的第 2 步后出现下面这个报错,也就是:
"UNPROTECTED PRIVATE KEY FILE!"
这是因为在 Mac 系统中,为了安全起见,SSH 要求私钥文件(.pem)的权限必须是“私有的”。你现在的权限是 0644,太开放了,所以 SSH 拒绝使用它。

我们用接下来的两行命令解决:
- 修改密钥权限:
chmod 400 /这里的路径/你的密钥文件名.pem (如之前的步骤,将文件拖入即可,或是用之前的路径即可)
- 重新建立隧道:
ssh -i "/这里的路径/你的密钥文件名.pem" -N -L 18789:127.0.0.1:18789 ubuntu@你的公网IP
此时的状态:
- 如果命令运行后,终端没有任何报错输出,只是光标在下一行闪烁,那就说明隧道终于成功建立了!
- 千万不要关闭这个终端窗口,也不要按
Ctrl+C。
以上,基本上你就能成功链接 Clawbot 了,抓紧体验一下吧!
关于接入飞书
Claw 支持国外的这些通讯工具,但我们在国内使用起来的确不方便,所以,我们要接入到飞书中,至于为什么是飞书,因为微信严格,而飞书可以通过创建企业级应用的方式来接入对话,较为方便,首先,我们来到飞书的开发者平台:
飞书开放平台
应用敏捷开发,服务高效入驻。飞书开放平台致力于以先进的协同办公理念和产品助力企业成长,帮助企业打造愉悦高效的专属办公平台。

创建一个应用,名字什么的自己取就好,然后在进入后复制:ID 和 Secret

紧接着开通权限,开通消息与群组就行:

在事件与回调中,设为长连接:

服务器端配置
我在实际的配置中,我也想偷懒使用飞书插件,例如网上常见的命令:
clawdbot plugins install moltbot-feishu
又或者
clawdbot plugins install @m1heng-clawd/feishu
但可能是我运气不好,这两个都给我报错,我干脆放弃了这种一键安装,转而寻求自己手动配置😵,如果各位在一键安装中发生了报错找不到原因,可以回来我这里试试下面的手动安装方式。
进入到服务器终端:
- 创建目录:
mkdir -p ~/.clawdbot/secrets
- 写入你的 Secret(直接复制下面这行)
echo "输入你的密钥" > ~/.clawdbot/secrets/feishu_app_secret
- 锁定权限
chmod 600 ~/.clawdbot/secrets/feishu_app_secret
- 创建脚本并锁定
nano bridge.mjs # 粘贴代码并保存
nano package.json # 粘贴依赖配置并保存
- 往 Bridge.mjs 和 Package.json 中填入以下代码
bridge 代码:
/**
* Feishu ↔ Clawdbot Bridge
*
* Receives messages from Feishu via WebSocket (long connection),
* forwards them to Clawdbot Gateway, and sends the AI reply back.
*
* No public server / domain / HTTPS required.
*/
import * as Lark from '@larksuiteoapi/node-sdk';
import fs from 'node:fs';
import os from 'node:os';
import crypto from 'node:crypto';
import WebSocket from 'ws';
// ─── Config ──────────────────────────────────────────────────────
const APP_ID = process.env.FEISHU_APP_ID;
const APP_SECRET_PATH = resolve(process.env.FEISHU_APP_SECRET_PATH || '~/.clawdbot/secrets/feishu_app_secret');
const CLAWDBOT_CONFIG_PATH = resolve(process.env.CLAWDBOT_CONFIG_PATH || '~/.clawdbot/clawdbot.json');
const CLAWDBOT_AGENT_ID = process.env.CLAWDBOT_AGENT_ID || 'main';
const THINKING_THRESHOLD_MS = Number(process.env.FEISHU_THINKING_THRESHOLD_MS ?? 2500);
// ─── Helpers ─────────────────────────────────────────────────────
function resolve(p) {
return p.replace(/^~/, os.homedir());
}
function mustRead(filePath, label) {
const resolved = resolve(filePath);
if (!fs.existsSync(resolved)) {
console.error(`[FATAL] ${label} not found: ${resolved}`);
process.exit(1);
}
const val = fs.readFileSync(resolved, 'utf8').trim();
if (!val) {
console.error(`[FATAL] ${label} is empty: ${resolved}`);
process.exit(1);
}
return val;
}
const uuid = () => crypto.randomUUID();
// ─── Load secrets & config ───────────────────────────────────────
if (!APP_ID) {
console.error('[FATAL] FEISHU_APP_ID environment variable is required');
process.exit(1);
}
const APP_SECRET = mustRead(APP_SECRET_PATH, 'Feishu App Secret');
const clawdConfig = JSON.parse(mustRead(CLAWDBOT_CONFIG_PATH, 'Clawdbot config'));
const GATEWAY_PORT = clawdConfig?.gateway?.port || 18789;
const GATEWAY_TOKEN = clawdConfig?.gateway?.auth?.token;
if (!GATEWAY_TOKEN) {
console.error('[FATAL] gateway.auth.token missing in Clawdbot config');
process.exit(1);
}
// ─── Feishu SDK setup ────────────────────────────────────────────
const sdkConfig = {
appId: APP_ID,
appSecret: APP_SECRET,
domain: Lark.Domain.Feishu,
appType: Lark.AppType.SelfBuild,
};
const client = new Lark.Client(sdkConfig);
const wsClient = new Lark.WSClient({ ...sdkConfig, loggerLevel: Lark.LoggerLevel.info });
// ─── Dedup (Feishu may deliver the same event more than once) ────
const seen = new Map();
const SEEN_TTL_MS = 10 * 60 * 1000;
function isDuplicate(messageId) {
const now = Date.now();
// Garbage-collect old entries
for (const [k, ts] of seen) {
if (now - ts > SEEN_TTL_MS) seen.delete(k);
}
if (!messageId) return false;
if (seen.has(messageId)) return true;
seen.set(messageId, now);
return false;
}
// ─── Talk to Clawdbot Gateway ────────────────────────────────────
async function askClawdbot({ text, sessionKey }) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(`ws://127.0.0.1:${GATEWAY_PORT}`);
let runId = null;
let buf = '';
const close = () => { try { ws.close(); } catch {} };
ws.on('error', (e) => { close(); reject(e); });
ws.on('message', (raw) => {
let msg;
try { msg = JSON.parse(raw.toString()); } catch { return; }
// Step 1: Gateway sends connect challenge → we authenticate
if (msg.type === 'event' && msg.event === 'connect.challenge') {
ws.send(JSON.stringify({
type: 'req',
id: 'connect',
method: 'connect',
params: {
minProtocol: 3,
maxProtocol: 3,
client: { id: 'gateway-client', version: '0.2.0', platform: 'macos', mode: 'backend' },
role: 'operator',
scopes: ['operator.read', 'operator.write'],
auth: { token: GATEWAY_TOKEN },
locale: 'zh-CN',
userAgent: 'feishu-clawdbot-bridge',
},
}));
return;
}
// Step 2: Connect response → send the user message
if (msg.type === 'res' && msg.id === 'connect') {
if (!msg.ok) { close(); reject(new Error(msg.error?.message || 'connect failed')); return; }
ws.send(JSON.stringify({
type: 'req',
id: 'agent',
method: 'agent',
params: {
message: text,
agentId: CLAWDBOT_AGENT_ID,
sessionKey,
deliver: false,
idempotencyKey: uuid(),
},
}));
return;
}
// Step 3: Agent run accepted
if (msg.type === 'res' && msg.id === 'agent') {
if (!msg.ok) { close(); reject(new Error(msg.error?.message || 'agent error')); return; }
if (msg.payload?.runId) runId = msg.payload.runId;
return;
}
// Step 4: Stream the response
if (msg.type === 'event' && msg.event === 'agent') {
const p = msg.payload;
if (!p || (runId && p.runId !== runId)) return;
if (p.stream === 'assistant') {
const d = p.data || {};
if (typeof d.text === 'string') buf = d.text;
else if (typeof d.delta === 'string') buf += d.delta;
return;
}
if (p.stream === 'lifecycle') {
if (p.data?.phase === 'end') { close(); resolve(buf.trim()); }
if (p.data?.phase === 'error') { close(); reject(new Error(p.data?.message || 'agent error')); }
}
}
});
});
}
// ─── Group chat intelligence ─────────────────────────────────────
//
// In group chats, only respond when the message looks like a real
// question, request, or direct address — avoids spamming.
function shouldRespondInGroup(text, mentions) {
if (mentions.length > 0) return true;
const t = text.toLowerCase();
if (/[??]$/.test(text)) return true;
if (/\\b(why|how|what|when|where|who|help)\\b/.test(t)) return true;
const verbs = ['帮', '麻烦', '请', '能否', '可以', '解释', '看看', '排查', '分析', '总结', '写', '改', '修', '查', '对比', '翻译'];
if (verbs.some(k => text.includes(k))) return true;
// Customize this list with your bot's name
if (/^(alen|clawdbot|bot|助手|智能体)[\\s,:,:]/i.test(text)) return true;
return false;
}
// ─── Message handler ─────────────────────────────────────────────
const dispatcher = new Lark.EventDispatcher({}).register({
'im.message.receive_v1': async (data) => {
try {
const { message } = data;
const chatId = message?.chat_id;
if (!chatId) return;
// Dedup
if (isDuplicate(message?.message_id)) return;
// Only handle text messages
if (message?.message_type !== 'text' || !message?.content) return;
let text = (JSON.parse(message.content)?.text || '').trim();
if (!text) return;
// Group chat: check if we should respond
if (message?.chat_type === 'group') {
const mentions = Array.isArray(message?.mentions) ? message.mentions : [];
text = text.replace(/@_user_\\d+\\s*/g, '').trim();
if (!text || !shouldRespondInGroup(text, mentions)) return;
}
const sessionKey = `feishu:${chatId}`;
// Process asynchronously
setImmediate(async () => {
let placeholderId = '';
let done = false;
// Show "thinking…" if reply takes too long
const timer = THINKING_THRESHOLD_MS > 0
? setTimeout(async () => {
if (done) return;
try {
const res = await client.im.v1.message.create({
params: { receive_id_type: 'chat_id' },
data: { receive_id: chatId, msg_type: 'text', content: JSON.stringify({ text: '正在思考…' }) },
});
placeholderId = res?.data?.message_id || '';
} catch {}
}, THINKING_THRESHOLD_MS)
: null;
let reply = '';
try {
reply = await askClawdbot({ text, sessionKey });
} catch (e) {
reply = `(系统出错)${e?.message || String(e)}`;
} finally {
done = true;
if (timer) clearTimeout(timer);
}
// Skip empty or NO_REPLY (trim to handle leading/trailing whitespace)
const trimmed = (reply || '').trim();
if (!trimmed || trimmed === 'NO_REPLY' || trimmed.endsWith('NO_REPLY')) {
// Clean up "thinking…" placeholder if it was sent
if (placeholderId) {
try {
await client.im.v1.message.delete({ path: { message_id: placeholderId } });
} catch {}
}
return;
}
// If we sent "thinking…", update it; otherwise send new message
if (placeholderId) {
try {
await client.im.v1.message.update({
path: { message_id: placeholderId },
data: { msg_type: 'text', content: JSON.stringify({ text: reply }) },
});
return;
} catch {
// Fall through to send new
}
}
await client.im.v1.message.create({
params: { receive_id_type: 'chat_id' },
data: { receive_id: chatId, msg_type: 'text', content: JSON.stringify({ text: reply }) },
});
});
} catch (e) {
console.error('[ERROR] message handler:', e);
}
},
});
// ─── Start ───────────────────────────────────────────────────────
wsClient.start({ eventDispatcher: dispatcher });
console.log(`[OK] Feishu bridge started (appId=${APP_ID})`);
Package.json 代码
{
"name": "feishu-clawdbot-bridge",
"version": "1.0.0",
"description": "Connect Feishu/Lark bot to Clawdbot agent via WebSocket — no public server needed",
"type": "module",
"scripts": {
"start": "node bridge.mjs",
"setup": "node setup-service.mjs"
},
"dependencies": {
"@larksuiteoapi/node-sdk": "^1.56.1",
"ws": "^8.18.0"
},
"engines": {
"node": ">=18"
},
"license": "MIT"
}
- 安装环境依赖:
npm install @larksuiteoapi/node-sdk ws
- 回到终端首页启动测试
FEISHU_APP_ID=你的ID node bridge.mjs
结束
以上,你就完成了 Clawbot 的部署和飞书的接入,其实按道理来说,微信也是可以接入的。但因为微信的管控非常严格,很难避免封号的情况。
所以目前在国内比较稳妥的方案是接入到 Feishu(飞书)。如果你有更多国外的渠道或方法,使用国外的平台还是会有更原生的体验。
关于中转 API 的配置
01、获取 API 凭证
你需要从中转 API 这儿拿到两个信息:
- API Base URL:
https://www.dmxapi.cn - API Key: sk
_xxxxxxxxxxxxx
推荐中转 API 服务商: DMXAPI(👈点击进入)
02、修改配置文件
以下所有的操作都是在我们之前启动的亚马逊的服务器终端修改,直接在终端输入下方的参数即可:
步骤1:备份配置文件