1. 这个Skill解决什么问题AI对话与本地备忘录之间的最后一公里你有没有过这种时刻让AI帮你把一段碎片想法扩成一份条理清晰的记录生成内容很满意结果发现把它从聊天窗口挪进备忘录这件事居然还要自己动手。如果AI能直接写进本地备忘录整个流程才算真正闭环。OpenClaw的apple-notes Skill解决的就是这个问题——它让AI助手可以直接在你的Mac上操作Apple备忘录Notes应用创建、搜索、读取、更新笔记把备忘录变成AI的外接记忆库。我给这个Skill装上之后的第一反应是早该这么干了。先交代一下背景。OpenClaw是一个开源的个人AI助手框架简单理解就是一层网关它把大模型的能力和你本机的工具环境接起来模型拿到你的指令后可以调用你机器上的命令、脚本和第三方服务。框架里的Skill机制是这套系统的核心——每个Skill就是一个可插拔的能力包通常由一个描述文件加若干可执行脚本组成。模型会先读描述文件判断当前这个请求该不该用这个技能确认后就执行脚本再把脚本返回的结果整理成回答。apple-notes Skill在这个体系里的定位非常明确给模型一个操作备忘录的入口。为什么这个需求值得做成一个专门的Skill因为备忘录在大多数人的工作流里其实承担的是外部存储和任务暂存区两个角色。灵感来了先扔进去会议记录往里归档读书摘抄往里堆待办事项往里记。这些场景有一个共同点输入内容往往是即时的、碎片化的而且你人在聊天窗口里手不一定在键盘上。如果AI在对话中就能顺手把内容落到备忘录里你就不需要经历复制→切App→粘贴→整理格式这一串动作。反过来当你想查一条旧笔记但又记不清放哪了直接让AI去备忘录里搜一遍比你自己翻文件夹高效得多。我用它解决的实际场景大致有这么几类灵感速记聊天时迸出来的想法让AI整理成条目写进备忘录标注好日期和标签。会议纪要归档会议中随口说的要点让AI扩展成结构化纪要放进指定项目文件夹。待办转笔记和老朋友约了周六爬山直接说帮我记到备忘录里周六和老朋友爬山它会自动生成带日期的笔记。读书摘抄和长文素材让AI把某个话题的搜索结果压缩成摘要落到备忘录留作素材。那为什么选备忘录而不是让AI直接写文件我之前也试过让模型往本地目录写Markdown文件效果倒是也行但备忘录有它不可替代的优势它是系统自带的开机就同步手机上随时看得到支持文件夹和标签全文搜索速度也快。最关键的它不需要你去申请任何第三方API的OAuth凭据本机的AppleScript接口是现成的。缺点同样明显笔记正文在脚本层面是HTML字符串格式处理容易踩坑附件几乎没法通过脚本操作这些后面我都会展开讲。有一点要先说明不同版本的OpenClaw对Skill目录和描述文件的格式要求略有差异我这里讲的是目前主流版本的做法核心思路是一致的。反正我在自己机器上反复测过这套方案跑得稳接下来我把整个实现链路、代码和踩过的坑一次性整理出来。2. 底层原理SKILL.md、osascript与备忘录App的协作链路2.1 Skill包的标准目录结构一个规范的apple-notes Skill目录结构大致长这样skills/apple-notes/ ├── SKILL.md ├── config.json └── scripts/ ├── create_note.sh ├── search_notes.sh ├── read_note.sh └── update_note.sh其中SKILL.md是给模型看的说明书决定了模型什么时候该调用这个技能、怎么调。config.json存一些可选参数比如默认笔记账号、默认文件夹、超时时间等。scripts目录下就是真正干活的脚本模型通过执行这些脚本来操控备忘录。整个机制可以这样理解OpenClaw启动后框架会把Skill列表暴露给模型模型看到SKILL.md里的描述后在合适的时机调用对应脚本。所以SKILL.md写得好不好直接决定模型想不想用这个技能。描述写得太宽泛模型会乱调写得太窄该用的时候又不会主动用。这是个需要反复调校的活后面我会给出一份经过实测的描述模板。2.2 驱动AppleScript的两种姿势AppleScript与JXAmacOS系统几乎每个原生App都暴露了一套AppleScript接口备忘录也不例外。调用方式有两种传统AppleScript语法以及JavaScript for Automation简称JXA也就是用JavaScript写AppleScript逻辑。两者最终都是通过osascript命令交给系统去执行。我做了几个月的备忘录Skill之后强烈推荐优先用JXA。原因很实际JXA可以用对象和数组操作数据筛选逻辑写起来比AppleScript的whose要直观得多而且JXA能直接跑正则、字符串处理这对清洗备忘录返回的HTML正文有决定性的帮助。传统AppleScript在处理复杂字符串拼接和条件分支时语法非常反直觉容易写出能跑但看不懂的代码。选型之后调用入口统一走/usr/bin/osascript -l JavaScript这样脚本内部不依赖任何第三方环境只要机器上有系统自带的命令行工具就能跑。2.3 一次请求的完整调用链我实际使用中一次帮我把这段话存到备忘录的请求背后大概走这么一条链路你把指令发给OpenClaw框架把指令连同可用的Skill描述交给大模型。模型读完SKILL.md后判断这个请求属于创建笔记场景于是决定调用create_note.sh。框架在本地shell里执行bash scripts/create_note.sh 标题 正文。脚本内部调用osascript -l JavaScript向备忘录App发送Apple事件。备忘录收到事件后执行创建操作返回结果给脚本。脚本把结果打印到标准输出框架把输出回传给模型。模型把原始输出整理成一句人话告诉你比如已经帮你记好了标题是xxx。这条链路里最容易出问题的环节是第4步和第5步之间的自动化授权。macOS对App之间的自动化控制管得很严首次执行时系统会弹窗询问是否允许当前终端应用控制备忘录这一步没处理好后面全是权限报错。这块我会在第5章单独讲。# 最小可用的创建脚本示例先跑通链路 #!/bin/bash # scripts/create_note.sh TITLE$1 BODY$2 /usr/bin/osascript -l JavaScript EOF /tmp/apple_notes_result.txt 21 const app Application(Notes); app.includeStandardAdditions true; const note app.make({ new: note }); note.name \${TITLE}\; note.body \h1${TITLE}/h1p${BODY}/p\; EOF cat /tmp/apple_notes_result.txt这个脚本是最朴素的版本直接通过here-doc把变量嵌进JavaScript里。它有个隐患如果标题或正文里出现反引号、美元符、引号这些特殊字符脚本会爆炸。所以我在正式版里改成用base64传参后面踩坑部分会详说。3. 安装与最小可用配置先跑通一条笔记再谈优化3.1 环境准备与目录创建在动手之前先确认几个前提机器是macOS备忘录已经用你的Apple ID登录过iCloud同步是开的。命令行基本工具可用osascript是系统自带的不需要额外安装。OpenClaw框架本身已经装好并且你清楚它的Skill目录在哪。有一类坑值得提前说别把OpenClaw跑在Docker容器里然后指望脚本能控制本机备忘录。Apple Events机制需要进程在图形会话里有权限容器里跑osascript大概率要么超时要么报权限错误。我自己试过在容器里调用结果被自动化授权卡得死死的。所以这个Skill的宿主机必须是本机的macOS环境哪怕OpenClaw跑在远程服务器上也要让脚本在Mac那侧执行。准备好之后在OpenClaw的skills目录下创建apple-notes文件夹然后建好scripts子目录mkdir -p skills/apple-notes/scripts3.2 SKILL.md的推荐写法SKILL.md由两部分组成frontmatter元信息区和正文描述区。frontmatter决定了模型在什么条件下能看到这个技能正文告诉模型具体怎么用。我目前用的版本是这样--- name: apple-notes description: 操作Apple备忘录Apple Notes的能力。当用户需要把内容保存到本地笔记、查询往期笔记、整理碎片信息、按关键词检索备忘录内容时使用。支持创建新笔记、搜索笔记、读取笔记正文、追加更新笔记内容。 ---正文部分可以写上如何使用和注意事项# Apple备忘录操作Skill ## 何时使用 - 用户明确要求记到备忘录/笔记里 - 用户想查找以前记录过但想不起来具体内容的信息 - 需要把一段较长的AI生成内容归档到本地 ## 如何使用 1. 确定操作类型创建、搜索、读取、更新 2. 调用对应脚本传入必要参数 3. 脚本输出结果后用通俗语言向用户总结 ## 注意事项 - 标题建议使用YYYY-MM-DD 主题格式 - 创建笔记时正文第一行会自动作为显示标题脚本里设置name属性的行為不一定生效 - 如果搜索结果为空不要编造内容如实告诉用户没找到这个描述模板的重点是何时使用部分写得很具体模型不会把备忘录技能滥用到改日历或者发邮件这些场景上。description字段开头就点出操作Apple备忘录模型在判断时能快速匹配。3.3 第一次跑通的完整步骤安装脚本之前先在命令行里直接测试最小脚本这样可以绕开OpenClaw本身的问题定位更干净cd skills/apple-notes bash scripts/create_note.sh 测试笔记 这是一条测试内容首次执行时macOS会弹出一个对话框问是否允许终端控制备忘录。这里必须点允许。如果你用的是iTerm或者其他终端模拟器弹窗里显示的名字会是对应的App。选错或者点了不允许后面会一直报权限错误需要去系统设置里重新授权。弹窗处理完后脚本应该没有任何报错输出。打开备忘录App你能看到一条标题为测试笔记的笔记正文包含那条测试内容。到这里最基本的链路就通了。然后把这个apple-notes目录放回OpenClaw的skills目录重启框架再用自然语言让AI帮我记一条笔记周六下午和老朋友在公园碰头。这时候模型会读取SKILL.md决定调用create脚本你就能在备忘录里看到它自动生成的笔记。第一次跑通的时候实际执行时间大概在2到3秒其中大部分耗在osascript启动和备忘录响应上。我在这个过程里发现一个很重要的规律尽量先绕过框架单独调试脚本再接入OpenClaw排查。因为框架层的报错信息往往把真正的脚本报错包了一层直接看脚本输出反而更快。4. 核心操作拆解查、读、写、改四项能力的实现与细节一个真正可用的apple-notes Skill光会创建笔记是不够的。我按实际使用频率排序给Skill补齐了四类操作创建、搜索、读取、更新。下面逐个讲实现方法和踩过的细节坑。4.1 创建笔记注意标题与正文的关系创建是使用频率最高的操作。上面那个最小脚本能跑但我在实际使用中很快发现一个问题脚本里设置的note.name属性并不总是能决定备忘录里显示的那行标题。备忘录的规则是显示标题取正文第一行name属性只是内部字段。如果你把name设成标题A但正文第一行是内容BApp里显示的很可能就是内容B。这就是为什么我在SKILL.md的注意事项里专门写了这一点。正确的做法是构造body时把标题作为第一行并且用标题级别的HTML标签包起来// 正确的创建逻辑正文首行为标题 const app Application(Notes); const note app.make({ new: note }); const bodyHtml h1${title}/h1p${content.split(\n).join(/pp)}/p; note.body bodyHtml;这样打开备忘录时显示列表里出现的标题就是你要的那个。这是个典型的、文档里基本不会写的AppleScript行为细节不实际踩一遍很难注意到。4.2 搜索笔记宁可全量拉取也别依赖whose过滤器搜索是备忘录最重要的能力因为信息一旦积累多了检索比创建更难。我最初写搜索脚本时很自然地想到用JXA的whose过滤器类似这样const app Application(Notes); const results app.notes.whose({ body: { _contains: 关键词 } });但这个过滤器在备忘录上的表现很不稳定有时候能搜到有时候一条都搜不到而且你根本无法判断到底是真没有结果还是过滤器失灵。测试了几轮之后我放弃了whose改成全量拉取再在脚本里过滤const app Application(Notes); const all app.notes(); const keyword args[0].toLowerCase(); const matches []; for (let i 0; i all.length; i) { const name all[i].name(); const body all[i].body(); if (name.toLowerCase().includes(keyword) || body.toLowerCase().includes(keyword)) { matches.push({ name, id: all[i].id() }); } }全量拉取看起来笨但胜在稳定可靠。备忘录几千条笔记、每条正文几百字的量级下脚本运行时间也就一两秒完全能接受。如果你积累了上万条带大附件的笔记再考虑用whose做第一层粗筛但基本盘还是全量过滤。搜索脚本的输入是关键词输出我让它只打印笔记标题和ID不打印正文避免返回数据量太大导致模型上下文被撑爆。4.3 读取笔记HTML正文的清洗是关键备忘录App里你看到的是富文本但AppleScript拿到的是HTML字符串。直接把这个HTML丢给模型一方面浪费token另一方面模型读起来费劲容易被一堆标签干扰。所以读取脚本里必须做HTML清洗。我的清洗函数长这样function cleanHtml(html) { return html .replace(/[^]/g, ) // 去标签 .replace(/nbsp;/g, ) .replace(/amp;/g, ) .replace(/lt;/g, ) .replace(/gt;/g, ) .replace(/quot;/g, ) .replace(/\s/g, ) .trim(); }注意一点备忘录返回的body有时候会带htmlbody包裹有时候是片段所以不能假设结构。清洗后的文本我还会顺手做一次换行恢复——把/p、/div替换成换行符让输出更接近人眼看到的排版。这一步做得好不好直接影响模型对笔记内容的理解质量。4.4 更新与追加避免整篇格式崩坏追加内容的场景很常见你让AI把一段新想法补到昨天那条笔记后面。AppleScript没有追加接口只能先读取整篇body拼上新的HTML片段再整体写回。这个操作最怕的是原笔记的HTML结构本身就乱——备忘录导入的网页内容经常带一堆嵌套标签你直接把新内容拼在末尾容易出现格式错乱。我的做法是读取原始body后先统一处理成干净的HTML结构。如果原body已经是完整的HTML文档有htmlbody就在/body前插入新内容如果只是片段就直接拼接。为了减少格式风险追加的内容只用最简单的p标签包裹不要嵌套复杂结构。另外追加类操作在执行前我会让模型先给用户确认一次将要追加的内容预览出来用户点头再执行。虽然多了一轮交互但能避免AI自作主张覆盖掉你辛苦记的笔记。这四类操作汇总起来就是一张实用的对照表操作脚本输入输出创建create_note.sh标题、正文新笔记ID搜索search_notes.sh关键词匹配的标题与ID列表读取read_note.sh笔记ID清洗后的纯文本正文更新update_note.sh笔记ID、追加内容更新后的笔记摘要5. 权限、隐私与边界macOS自动化授权里的三道关卡5.1 自动化授权链路弹窗、系统设置与宿主AppmacOS从Mojave开始对App之间的自动化控制加了权限管控。你的终端第一次通过osascript控制备忘录时系统会弹出终端想要控制备忘录的对话框。这个弹窗的背后是自动化权限归在系统设置的隐私与安全性分类下。如果第一次误点了不允许后续再执行脚本就会静默失败不报什么明确错误但脚本就是没效果。处理方式有两步先打开系统设置里的隐私与安全性 → 自动化找到你的终端App和备忘录这一对授权条目把开关打开然后杀掉终端进程重启刷新授权状态。有些情况下授权条目不会自动出现你需要在命令行随便跑一次osascript触发弹窗授权的条目才会被登记。这里有个关键细节要注意授权的主体是你的终端App而不是osascript本身。如果你用系统自带终端就是终端App用iTerm就是iTerm。如果你把OpenClaw做成开机自启动的服务宿主变成了launchd而不是终端那授权关系又会变。我是建议在调试期间就用前台终端跑确认全链路稳定后再考虑服务化。5.2 不需要的权限Full Disk Access与屏幕录制很多人一听说要操控系统App就以为需要开完全磁盘访问权限或者屏幕录制权限。实际上完全不需要。备忘录的AppleScript接口只暴露给自动化控制它拿到的只是备忘录数据库里允许脚本访问的那部分内容不需要系统级的磁盘权限更不需要屏幕录制。但反过来说脚本能访问的范围也被限制在AppleScript接口暴露的字段里。举例来说笔记里的图片附件脚本可以知道它的文件名但拿不到附件的二进制数据更不可能通过脚本把图片导出到本地。想做附件级操作只能另想办法比如通过macOS的快捷指令配合或者用文件系统层面的数据库直读方案。后者风险高我不推荐。5.3 Skill的边界什么不该做与隐私红线备忘录承载的往往是个人最私密的信息。这个Skill在你机器上运行意味着你对话中提到的内容会被模型处理。虽然OpenClaw可以接到本地模型或API上但一旦走云端API笔记内容就会被发送到模型服务商那边。这是所有本地AI助手都得面对的隐私权衡我在配置里明确加了一条规则涉及密码、卡号、敏感身份信息的笔记操作Skill应该拒绝执行并提醒用户。设计边界时我坚决不给这个Skill开放两个能力删除笔记和批量改写。删除操作风险太高AI一旦理解错指令可能把你整个归档干掉批量改写容易破坏原有结构。真需要删除或大规模调整我会让脚本只输出一条带完整笔记ID和标题的确认信息由用户手工完成。这个边界规则写进SKILL.md的注意事项里模型在决策时会自动规避。另外还有一种隐蔽风险脚本输出如果带了大量笔记正文模型的上下文窗口会被占用。我在每个脚本的输出里做了截断搜索只输出标题和ID读取只输出前两千个字符超出部分提示笔记过长已截断。这样既保证功能完整又避免token浪费。6. 踩坑复盘五个高频问题与完整排查链路6.1 osascript被拒绝类错误权限问题的完整定位这个坑出现的频率最高症状也最迷惑脚本执行后什么都不输出备忘录里也没有任何变化。第一次遇到时我在终端里傻站了好几分钟才反应过来。排查链路如下先在终端直接跑/usr/bin/osascript -l JavaScript -e Application(Notes).name()看控制台有没有输出-1743之类的错误码。-1743是典型的Apple事件权限拒绝码。如果报权限错误去系统设置隐私与安全性→自动化确认终端App和备忘录的授权条目是打开状态。如果授权条目压根不存在回到终端触发一次弹窗授权后再重试。如果权限已经开了但还是不行检查你是不是用了sudo执行osascript。sudo会把进程的身份切成root而root没有Apple事件授权结果就是依然被拒。最后检查备忘录App本身是否处于正常状态比如没有登录账户、开了多个窗口卡死等。这套链路我整理成笔记挂在触手可及的地方每次换新机器都要走一遍。6.2 中文与特殊字符的转义灾难用base64一劳永逸直接把标题和正文嵌进JXA脚本里中文一般没问题但遇到下面这些字符就疯了反引号、${}插值、单双引号、美元符、反斜杠。你的待办事项里极有可能出现这类内容比如和老朋友约了看$5电影直接能把脚本字符串炸掉。我最终的解决方案是绕开字符串拼接把数据用base64编码后传给脚本# 调用侧 BODY_B64$(printf %s $BODY | base64) /usr/bin/osascript -l JavaScript scripts/create_from_b64.js $BODY_B64JXA脚本内部先把base64解码成原始字符串再转成UTF-8处理。这样不管内容里有什么特殊字符都不会影响脚本本身的语法。实测下来这一步把问题字符的报错率降到了零。6.3 正文返回HTML导致的格式污染第一次读取笔记时我以为拿到的是纯文本直接让模型读结果模型说笔记内容全是尖括号标签。后来才搞清楚备忘录的body字段本质是HTML而且格式并不统一有的笔记是htmlbody包裹有的是div片段有的是富文本粘贴生成的乱七八糟结构。解决思路就是前面第4.3节写的清洗函数但还有一个补充细节清洗后的文本顺序可能和视觉顺序不一致。备忘录内部存储的HTML结构偶尔会把文本内容拆成多个块正则去标签后块与块之间没有换行导致读起来是一整段糊的。所以我在清洗函数里把所有块级标签的结束位置都额外补一个换行符尽量还原视觉排版。6.4 iCloud同步延迟刚建的笔记搜不到创建笔记后立刻搜索经常搜不到这个问题一度让我以为是搜索脚本写错了。后来发现是iCloud同步的锅新建的笔记先落本地但全文索引还没跟上body字段也还没完全刷新。刚创建完就全量拉取拿到的可能是个空body。处理办法是在创建脚本末尾加一个短暂的等待和重试。我设置了最多5秒的轮询创建后每隔1秒读取一次这条笔记的body直到body非空或者超时。这样虽然多花一两秒但后续搜索的可靠性大幅提升。这个等待逻辑我放在了后台执行对用户来说感知不到延迟。6.5 标题不听话name属性与显示标题的错位这是第4.1节提到的坑在这里展开完整链路。现象脚本设置了note.name 周一待办备忘录列表显示的却是正文第一行的内容。我最初以为是设置顺序的问题试过先设body再设name、或者反过来都不管用。查了相关资料才明白备忘录的设计逻辑是显示标题动态取正文首行name只是脚本接口里的字段最终显示优先级低于正文首行。解决办法就是我在创建脚本里做的把标题同时作为name和body的第一行h1内容写入。这样无论备忘录内部怎么解析显示出来的标题都是统一的。7. 进阶玩法把备忘录变成AI的长期记忆库7.1 每日摘记工作流当创建、搜索、读取这些基础操作都稳定之后我开始琢磨怎么把备忘录真正变成长期记忆库而不是零散地存几条笔记。我给自己设计了一个每日摘记模板每天一条笔记标题是当天日期正文分三个区块——今日灵感、今日完成任务、今日待整理。这个模板的价值在于它给AI一个稳定的写入结构。你跟AI说记一下今天下午想到的产品方案思路它能识别出这是灵感直接追加到当日笔记的今日灵感区块而不是每次生成一条新笔记。长期下来所有碎片信息都按时间归档检索时按日期范围筛选就非常高效。7.2 会议纪要自动归档另一个实用的进阶玩法是会议纪要流。我让AI在获得原始会议记录后先提炼成结论、行动项、责任人、截止时间四段式纪要然后以项目名-会议日期的格式创建一条笔记并归入对应的项目文件夹。这里涉及到一个脚本能力指定文件夹创建笔记。实现上需要先遍历app.accounts里的账户再找到对应的folders最后在目标文件夹下make新笔记。文件夹路径如果不存在脚本会如实报错不会自动创建新文件夹——这是我有意做的限制避免AI随手建一堆乱七八糟的文件夹。7.3 命名规范与标签约定为了检索效率我强制了一套命名约定主题笔记用YYYY-MM-DD 主题格式项目笔记用项目名-主题格式所有临时想法统一打上同一个标签。这套约定写进了SKILL.md模型在创建笔记时会自动套用。我强烈建议即使你只自己用也给备忘录制定简单的命名规范否则三个月后笔记积累到几百条搜索靠关键词会越来越费劲。7.4 我的日常使用体会最后分享一点个人经验。这个Skill真正让工作流产生质变的不是AI能写备忘录这件事本身而是我敢于把所有临时想法都丢给AI去归档因为我知道它一定会落到一个统一、可检索的结构里不会丢。过去我的备忘录是想到才打开现在它成了AI的默认落地位置。如果你也想搭一套我的建议是别一上来就追求完整功能先装好创建脚本和搜索脚本用起来再根据实际使用中发现的不顺手逐步补功能。这个Skill的价值是随着笔记数量增长而不断放大的当你发现随口一句让AI记下来已经变成习惯时你就知道它值了。