造个很奇怪的东东:CodexCLI完成后通过飞书机器人提醒你

需要一点个人的小小捣鼓文档的能力…纯水版本,等后面CC的也造出来了,发个一步步版本

config.toml
notify = ["python", "C:/Users/你的用户名/.codex/notify_template.py"]

如何新建机器人
  1. 开发者后台 - 飞书开放平台


3.

4. 可以去这里拿到相关的参数
发送消息 - 服务端 API - 开发文档 - 飞书开放平台

导入飞书的卡片

新建个CodexCLI项目已完工.card,名字随意,内容

{"name":"CodexCLI 项目已完工","dsl":{"schema":"2.0","config":{"update_multi":true,"style":{"text_size":{"normal_v2":{"default":"normal","pc":"normal","mobile":"heading"}}}},"body":{"direction":"vertical","elements":[{"tag":"markdown","content":"**CodexCLI已完成需求**","margin":"0px 0px 0px 0px","element_id":"Ovh10jYri6DsF7m9U06A"},{"tag":"hr","margin":"0px 0px 0px 0px"},{"tag":"markdown","content":"#### ${title}","text_align":"left","text_size":"normal_v2","margin":"0px 0px 0px 0px"},{"tag":"picker_time","placeholder":{"tag":"plain_text","content":""},"width":"default","initial_time":"${time}","margin":"0px 0px 0px 0px"}]},"header":{"title":{"tag":"plain_text","content":"CodexCLI 已完工"},"subtitle":{"tag":"plain_text","content":""},"text_tag_list":[{"tag":"text_tag","text":{"tag":"plain_text","content":"已完成"},"color":"green"}],"template":"blue","icon":{"tag":"standard_icon","token":"check-circle_outlined"},"padding":"12px 8px 12px 8px"}},"variables":[{"type":"time","apiName":"var_mkly9why","name":"time","desc":"","mockData":"10:04"},{"type":"text","apiName":"var_mkly9wi9","name":"title","desc":"","mockData":"测速"}]}

  1. 飞书卡片搭建工具


记得复制ID给下面用

notify_template.py


import sys
import json
import time
import urllib.request
import urllib.error

# ==================== ⚙️ 核心配置 (请填这里) ====================

# [1] 飞书应用凭证 (自建应用 -> 凭证与基础信息)
# 这次我们填 ID 和 Secret,不填那个会过期的 Token 啦!
APP_ID = "" 
APP_SECRET = ""

# [2] 接收人与模板
RECEIVE_ID = ""       # 你的 User ID
TEMPLATE_ID = "" # 你的卡片模板 ID

# [3] AI 概括配置
SUMMARY_API_URL = "https://open.bigmodel.cn/api/paas/v4/chat/completions" 
SUMMARY_API_KEY = "" 
SUMMARY_MODEL = "glm-4-flash-250414" 

# ===============================================================

def get_tenant_access_token():
    """
    Step 1: 动态获取飞书 Token
    文档参考: https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal
    """
    url = "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal"
    headers = {"Content-Type": "application/json; charset=utf-8"}
    data = {
        "app_id": APP_ID,
        "app_secret": APP_SECRET
    }
    
    try:
        req = urllib.request.Request(url, json.dumps(data).encode('utf-8'), headers)
        with urllib.request.urlopen(req, timeout=5) as response:
            res_json = json.loads(response.read().decode('utf-8'))
            
            if res_json.get("code") == 0:
                # 通常 internal app 使用 tenant_access_token 发消息最稳
                return res_json.get("tenant_access_token")
            else:
                print(f"❌ 获取 Token 失败: {res_json.get('msg')}")
                return None
    except Exception as e:
        return None

def call_summary_ai(last_command, assistant_output):
    """
    Step 2: 调用 AI 进行概括
    """
    system_prompt = (
        "你是一个技术助手。请根据用户的【最后一条指令】和系统的【执行结果】,"
        "生成一份简短的中文总结(100字以内)。"
        "要求:1. 总结核心改动。 2. 语气轻松,必须包含Emoji (✅, 🔧, 🚀)。 3. 不要废话,直接输出内容。"
    )

    user_content = f"【用户最后指令】: {last_command}\n【代码执行片段】: {assistant_output[:300]}"

    payload = {
        "model": SUMMARY_MODEL,
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_content}
        ],
        "temperature": 0.5,
        "max_tokens": 150
    }

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {SUMMARY_API_KEY}"
    }

    try:
        req = urllib.request.Request(SUMMARY_API_URL, json.dumps(payload).encode('utf-8'), headers)
        with urllib.request.urlopen(req, timeout=10) as response:
            res_json = json.loads(response.read().decode('utf-8'))
            summary = res_json['choices'][0]['message']['content'].strip()
            return summary.replace('"', '').replace('\n', ' ')
    except Exception as e:
        return None

def send_feishu_card(access_token, title_content, time_str):
    """
    Step 3: 发送卡片通知 (使用动态 Token)
    """
    url = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id"
    
    template_content = {
        "type": "template",
        "data": {
            "template_id": TEMPLATE_ID,
            "template_version_name": "1.0.2",
            "template_variable": {
                "title": title_content,
                "time": time_str
            }
        }
    }

    body = {
        "receive_id": RECEIVE_ID,
        "msg_type": "interactive",
        "content": json.dumps(template_content)
    }

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {access_token}" # 这里用刚才动态获取的 token
    }

    try:
        req = urllib.request.Request(url, json.dumps(body).encode('utf-8'), headers)
        with urllib.request.urlopen(req) as response:
            print(f"✅ 通知发送成功! Status: {response.status}")
    except urllib.error.HTTPError as e:
        print(f"❌ 发送消息失败: {e.read().decode('utf-8')}")
    except Exception as e:
        print(f"❌ 未知错误: {e}")

def main():
    # 1. 只有拿到了 Token 才能进行下一步
    token = get_tenant_access_token()
    if not token:
        return # 没 Token 啥也干不了,直接退出

    current_time = time.strftime("%H:%M", time.localtime())

    # 2. 解析 Codex 参数
    raw_json = "{}"
    if len(sys.argv) > 1:
        raw_json = sys.argv[1]
    try:
        payload = json.loads(raw_json)
    except:
        payload = {}

    # 3. 提取最后一条指令
    input_msgs = payload.get("input-messages", [])
    if isinstance(input_msgs, list) and len(input_msgs) > 0:
        last_command = str(input_msgs[-1])
    elif input_msgs:
        last_command = str(input_msgs)
    else:
        last_command = "未知任务"
    assistant_output = payload.get("last-assistant-message", "")
    # 4. AI 概括
    summary = call_summary_ai(last_command, assistant_output)
    final_title = summary if summary else last_command[:100]
    # 5. 发送通知
    send_feishu_card(token, final_title, current_time)

if __name__ == "__main__":
    main()





效果图:

45 个赞

可以愉快的摸鱼咯!

2 个赞

飞书/钉钉/server酱,都可以接一下

1 个赞

钟佬折腾不止啊,神速

1 个赞

钉钉用的少,企业微信本来打算搞得,但是我看到我N8N搭建的服务器报错还没解决…算了吧,看到就烦躁,server酱是啥

1 个赞

我目前是直接通过系统弹窗通知的,但是macos26自定义图标只能是sender

1 个赞

系统提示词我WIN弹不出来,试了几个佬写的了,干脆自己对接飞书了

1 个赞

太强了,zhongruan!

2 个赞
1 个赞

再开个–dangerously-skip-permissions,可以开始全自动摸鱼了

3 个赞

厉害啊(๑•̀ㅂ•́)و✧

2 个赞

有佬分享过类似的的项目哈哈

1 个赞

确实,他的看起来更高级,但是我还是喜欢我自己这个,不用额外安装东西

1 个赞

还提个需求,不止是 正常结束时要通知;
如果任务异常终止也要通知.

适合自己的才是最好的 :bili_019:

你这个也有很多我可以参考改进的亮点,回头改进一下 :bili_052:

是厉害的,钟佬

越来越方便了

学到辣 :saluting_face:

企业微信其实比较省事儿,不双向通讯的话用群机器人就可以了,就是简单的webhook。可能发送消息的美观程度有一些限制,不过不是主要矛盾。

有木有手机上操练codex的方式:joy: