# TOOLS.md - Local Notes Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup. ## What Goes Here Things like: - Camera names and locations - SSH hosts and aliases - Preferred voices for TTS - Speaker/room names - Device nicknames - Anything environment-specific ## Examples ```markdown ### Cameras - living-room → Main area, 180° wide angle - front-door → Entrance, motion-triggered ### SSH - home-server → 192.168.1.100, user: admin ### TTS - Preferred voice: "Nova" (warm, slightly British) - Default speaker: Kitchen HomePod ``` ## Why Separate? Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure. --- ## DingTalk Configuration - **AppKey**: ding4ursdp0l2giat4bj - **AppSecret**: J0gBicjKiIHoKla7WfKKhRs1Tv8L6Xd5UhW3EVQByF16G7Vn7UUcRhP6u-PBCQNo - **robotCode**: ding4ursdp0l2giat4bj - **OpenConversationId**: cidcjYshXVtKck5LfOO9AqOJg== ## DingTalk成功发送的关键配置 ### Token获取(必须使用GET + URL参数) ```javascript // ✓ 正确方式 const tokenUrl = `https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}`; const token = await axios.get(tokenUrl).data.access_token; // ✗ 错误方式(会失败) const tokenUrl = "https://oapi.dingtalk.com/gettoken"; const token = await axios.get(tokenUrl, { params: {appkey: APP_KEY, appsecret: APP_SECRET} }).data.access_token; ``` ### v1.0 API发送消息(成功方式) ```javascript // API端点 const url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send"; // Headers(关键:包含access_token) const headers = { "x-acs-dingtalk-access-token": accessToken, "Content-Type": "application/json" }; // Body格式 const body = { robotCode: ROBOT_CODE, openConversationId: OPEN_CONVERSATION_ID, msgKey: "sampleFile", // 或 "sampleMarkdown" msgParam: JSON.stringify({ // ← 必须是字符串! mediaId: media_id, fileName: "file.txt" }) }; await axios.post(url, body, { headers }); ``` ### 关键msgKey对应关系 | 媒体类型 | msgKey | 说明 | |---------|--------|------| | **文件** | `sampleFile` | 发送.txt, .docx, .pdf等文件 | | **图片** | `sampleMarkdown` | 使用Markdown格式嵌入图片 | | **视频** | `sampleVideo` | 发送.mp4等视频文件(待测试) | ### 重要注意事项 1. **msgParam必须是字符串** ```javascript // ✓ 正确 msgParam: JSON.stringify({mediaId: "@xxx", fileName: "test.txt"}) // ✗ 错误 msgParam: {mediaId: "@xxx", fileName: "test.txt"} ``` 2. **直接调用API会失败于OpenClaw环境** - OpenClaw的DingTalk插件会拦截所有API请求 - 直接调用`robot/send`会返回"token is not exist"或"缺少参数 access_token" - 必须使用v1.0 API + Headers中的access_token 3. **文件上传API** ```javascript // 上传(GET方式获取token已经验证有效) const uploadUrl = `https://oapi.dingtalk.com/media/upload?access_token=${accessToken}`; const form = new FormData(); form.append('media', fs.createReadStream(filePath)); form.append('type', 'file'); await axios.post(uploadUrl, form, {headers: form.getHeaders()}); ``` ### OpenClaw Runtime 环境说明 - 系统 Node 由 **nvm** 管理。 - OpenClaw 与 Skill 执行使用独立 Node:`F:/openclaw-runtime`,不受 nvm 切换影响。 - 系统 Python 由 **pyenv-win** 管理。 - OpenClaw 也使用独立 Python 运行环境,不受系统 pyenv 切换影响。 ### 成功案例记录 **2026-03-05 18:44** - 成功发送database_analysis_summary.txt - 文件: database_analysis_summary.txt (3,155 bytes) - media_id: @lAjPM2GaR32g2U3OC2pNq84Clg_0 - msgKey: sampleFile - API: /v1.0/robot/groupMessages/send - ProcessQueryKey: lXs/uLRd0YxJVk1x0VyTLfSdY2YKCE1yrGn2vGlQ+GM= ### 默认发图稳定流程(柏方确认) 当用户说“发图”时,默认按以下顺序执行: 1. `GET https://oapi.dingtalk.com/gettoken?appkey=...&appsecret=...` 获取 token 2. `POST https://oapi.dingtalk.com/media/upload?access_token=...` 上传图片,获取 `media_id` 3. `POST https://api.dingtalk.com/v1.0/robot/groupMessages/send` 发送消息 - Header: `x-acs-dingtalk-access-token: ` - `msgKey = sampleMarkdown` - `msgParam` 必须是 JSON 字符串(内嵌图片) Add whatever helps you do your job. This is your cheat sheet.