协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
flomo【开发与工程】flomo 浮墨笔记助手 — 读取/搜索/写入/编辑短笔记与灵感。通过本技能目录下的 flomo.py 脚本(私有 API + Webhook)操作,浏览器 UI 仅作兜底。适合碎片化快速记录;长笔记/知识管理用 getnote。
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
多副本同步提醒:本文件改完后,必须同步到以下两份,否则线上 agent 读到的仍是旧版:
- host:
~/.ethan/skills/flomo/SKILL.md- 容器内 volume(运行中 agent 实际读取):
/root/.ethan/skills/flomo/SKILL.md,用docker cp推送 三份不一致会导致「改了不生效 / 重建镜像后和 volume 对不上」。
flomo(本技能):短笔记、灵感、闪念、碎片化快速记录。仅在用户点名 flomo/浮墨,或语境明确是"记个灵感/闪念/卡片"时才用。
knowledge_*(系统知识库),不要默认跳到本技能完整路由见系统 tools.md 的「记录/笔记入口路由」表。
本技能目录下有 flomo.py 脚本,封装了 flomo 私有 API + Webhook,用 shell 工具调用即可,一次调用拿全量,比浏览器快几十倍。
脚本路径(两种环境都要支持):
/root/.ethan/skills/flomo/flomo.py~/.ethan/skills/flomo/flomo.py调用前先用 file_read 确认脚本路径存在;不存在则报错让用户同步技能目录。
| 子命令 | 功能 | 所需鉴权 | 说明 |
|---|---|---|---|
query | 搜索/查询笔记 | access_token | 支持 keyword/tag/days/start/end/limit |
tags | 列出标签及频率 | access_token | 支持 prefix/days/limit |
create | 创建笔记 | access_token | 支持 HTML 富文本 |
edit | 编辑已有笔记 | access_token | 需指定 slug 或 URL |
write | 写入笔记 | webhook key | 仅纯文本 + #标签 |
# 读取最近 10 条笔记
python3 ~/.ethan/skills/flomo/flomo.py query --limit 10
# 按关键词搜索
python3 ~/.ethan/skills/flomo/flomo.py query --keyword "认知" --limit 20
# 按标签过滤,最近 30 天
python3 ~/.ethan/skills/flomo/flomo.py query --tag "闪念/思考" --days 30
# 按日期范围
python3 ~/.ethan/skills/flomo/flomo.py query --start 2026-07-01 --end 2026-07-31
# 列出所有标签(前 50 个,按频率降序)
python3 ~/.ethan/skills/flomo/flomo.py tags
# 按前缀过滤标签
python3 ~/.ethan/skills/flomo/flomo.py tags --prefix "闪念"
# 创建笔记
python3 ~/.ethan/skills/flomo/flomo.py create --content "今天的想法 #闪念/思考"
# 编辑笔记(slug 从 query 结果取)
python3 ~/.ethan/skills/flomo/flomo.py edit --slug "NTAxMjM0" --content "更新后的内容 #闪念/思考"
# Webhook 写入(仅需 webhook key,不需要客户端登录)
python3 ~/.ethan/skills/flomo/flomo.py write --content "快速记一笔 #闪念/收集"
容器环境:路径换成
/root/.ethan/skills/flomo/flomo.py。容器的 Dockerfile 已带 python3 + 标准库,无需额外依赖。
脚本会自动按以下优先级提取鉴权信息:
access_token(query/tags/create/edit 用):
set_secret(name="flomo-access-token", value="<id|token>"))webhook key(write 用):
set_secret(name="flomo_webhook_key", value="<key>"))容器/无客户端环境:必须先用 secrets 配置 token,否则只有
write能用。本机装了客户端则全部自动可用。
set_secret 配置,照做后重试仅当 flomo.py 脚本不可用(文件缺失 / Python 环境异常 / 反复失败)时才走浏览器。
ethan serve 启动日志里打印,或 ~/.ethan/config.yaml 的 network.auth_token)连上服务(默认 8900)。连上后日志显示 browser ws: extension connected。https://v.flomoapp.com/mine/,不要用 api_key 兜底。flomo 首页 DOM 极重(侧边栏 400+ 标签),browser_page snapshot 或框架整页截图会把上下文/响应撑爆被 SIGKILL。所有操作一律用 browser_page eval 跑紧凑 JS、只回传小 JSON,绝不 snapshot / 整页截图。若单次调用仍 OOM,缩小返回内容后重试。
browser_tab 打开 https://v.flomoapp.com/mine/。browser_page eval 跑下面的脚本,取最新 N 条:(async () => {
const N = 5;
const cards = [...document.querySelectorAll('.display.showMemoInsight')];
const data = cards.slice(0, N).map(card => ({
text: (card.innerText || '').trim().slice(0, 2000),
images: [...card.querySelectorAll('img[src*="static.flomoapp.com"]')].map(i => i.src)
}));
return JSON.stringify(data);
})()
返回数组,每项含 text(正文含时间/标签)与 images(OSS 预签名直链,可直接 curl 下载,无需鉴权)。
首页折叠提醒:flomo 首页卡片默认折叠长文,DOM 拿到的
innerText常是片段而非全文(验证时多次踩到)。若text明显被截断、或用户要的是某条笔记完整内容,需先点开该卡片展开全文,再取完整innerText,不要自信地当成全文返回。
browser_tab 打开/聚焦 https://v.flomoapp.com/mine/。INPUT.el-input__inner[placeholder="⌘+K"],用 fill 写入关键词,等 ~500ms 或 press Enter。browser_page eval 抽取:(() => {
const cards = [...document.querySelectorAll('.display.showMemoInsight')];
const data = cards.map(card => ({
text: (card.innerText || '').trim().slice(0, 2000),
images: [...card.querySelectorAll('img[src*="static.flomoapp.com"]')].map(i => i.src),
tags: [...card.querySelectorAll('[tag]')].map(e => e.getAttribute('tag'))
}));
return JSON.stringify({ count: data.length, notes: data });
})()
比扫首页快很多;绝不 snapshot 整页(见全局 OOM 红线)。
用户问「我有哪些标签 / 某标签下有哪些子标签」时走这里。
用 browser_page eval 跑下面脚本(先读 Vuex store 取速,取不到回退 DOM 扫描):
实测注意:
window.__INITIAL_STATE__在 flomo 根本不存在(undefined)。真实 store 是window.__flomoVuexStore,但全量标签字段默认为空(需先开「标签管理」面板才填充)。最稳路径是 DOM 扫描(已验证可拿全量 417 个)。
(async () => {
const collectDOM = () => { const s = new Set(); document.querySelectorAll('[tag]').forEach(el => { const t = el.getAttribute('tag'); if (t) s.add(t); }); return s; };
try {
const st = window.__flomoVuexStore && window.__flomoVuexStore.state;
const tm = st && st.tagManager && st.tagManager.tags;
if (Array.isArray(tm) && tm.length) {
const paths = tm.map(x => typeof x === 'string' ? x : (x.name || x.tag || x.path || '')).filter(Boolean);
if (paths.length) return JSON.stringify({ route: 'store', tags: paths });
}
} catch (e) {}
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sb = document.querySelector('.sidebar') || document.querySelector('#sidebar');
const seen = collectDOM();
let last = -1, stable = 0;
for (let i = 0; i < 80; i++) {
const closed = Array.from(document.querySelectorAll('.tag-expand-button:not(.expanded), button[aria-expanded="false"]'));
if (closed.length) closed.forEach(b => b.click());
await sleep(120);
collectDOM().forEach(t => seen.add(t));
const atBottom = sb ? (sb.scrollTop + sb.clientHeight >= sb.scrollHeight - 5) : true;
if (seen.size === last && closed.length === 0 && atBottom) { if (++stable >= 4) break; } else stable = 0;
last = seen.size;
if (sb) sb.scrollTop += 800; else window.scrollBy(0, 800);
await sleep(120);
}
collectDOM().forEach(t => seen.add(t));
return JSON.stringify({ route: 'dom', tags: Array.from(seen) });
})()
返回 {"route":"store"|"dom","tags":[...]} —— tags 是完整路径字符串数组(如 ["领域/财富","work","mac",...])。
汇报:总数 + 顶层标签(不含 /)+ 用户问到的子树(按前缀 + / 层级匹配)。
browser_tab 打开 https://v.flomoapp.com/mine/。browser_page fill 把整段内容(含末尾 #标签)一次性写入 div.tiptap.ProseMirror(占位提示「现在的想法是...」)。browser_page click 点提交按钮 svg.saveBtn(输入为空时是 svg.disableSave,不可点)。写入前务必遵守下方「标签规范」,优先复用存量标签。
首页 DOM 极重,直接在首页操作极易耗尽步数。先在搜索框搜目标笔记唯一关键词,跳到搜索结果页再删,结果页 DOM 很轻。
browser_page eval 跑下面脚本(把 __目标文本__ 换成唯一片段):(async () => {
const sleep = ms => new Promise(r => setTimeout(r, ms));
const vis = el => el && el.offsetParent !== null;
const KEY = '__目标文本__';
const cards = [...document.querySelectorAll('.display.showMemoInsight')];
const target = cards.find(c => (c.innerText || '').includes(KEY));
if (!target) return JSON.stringify({ok:false, step:'locate', msg:'未找到目标笔记'});
const trigger = [...target.querySelectorAll('*')].find(b => {
if (!vis(b)) return false;
return /dropdown|more|operation|menu|ellipsis/i.test('' + b.className);
});
if (!trigger) return JSON.stringify({ok:false, step:'menu', msg:'没找到「...」菜单按钮'});
trigger.click();
await sleep(400);
const items = [...document.querySelectorAll('li,div,[role=menuitem],.el-dropdown-menu__item')].filter(vis);
const del = items.find(b => /删除/.test(b.innerText || ''));
if (del) del.click();
await sleep(500);
const cfrms = [...document.querySelectorAll('button,.el-button')].filter(vis).filter(b => /确定|删除/.test(b.innerText || ''));
const ok = cfrms.find(b => /确定/.test(b.innerText || '')) || cfrms[0];
if (ok) ok.click();
await sleep(800);
const after = [...document.querySelectorAll('.display.showMemoInsight')];
const gone = !after.some(c => (c.innerText || '').includes(KEY));
return JSON.stringify({ok:true, deleted: gone});
})()
安全护栏:
- KEY 必须足够唯一(优先用完整标签如
领域/思维模型),绝不用宽泛词。- 删除前先只读 eval 找出笔记,把原文念给用户确认,用户明确确认后再点删除。误删不可逆。
用户 flomo 现有 417 个标签,加笔记时优先复用存量标签。
| 类目 | 含义 | 常见子标签 |
|---|---|---|
#领域 | 知识 / 认知领域 | 领域/财富、领域/成长、领域/思维模型、领域/心理学、领域/写作 |
#项目 | 进行中的项目 | 项目/AI、项目/写作、项目/时间管理、项目/如何阅读 |
#输入 | 外部输入源 / 素材 | 输入/书、输入/电影、输入/得到、输入/帆书、输入/网络 |
#闪念 | 突发灵感、临时收集 | 闪念/思考、闪念/收集、闪念/生活、闪念/微信文章 |
#永久 | 长期沉淀的精华笔记 | 永久/思考、永久/素材、永久/名词、永久/沟通 |
work、mac、codetodo、fix、checklist、daily、archived旅游、日记、权益、小创新、资源、临时、试试# 开头、紧跟文字、无空格(#阅读 ✓,# 阅读 ✗)- 连接(如 读书笔记-认知觉醒)/(#阅读/认知觉醒),深度以 2-3 层为主用户问标签时优先走 flomo.py tags(最权威)。脚本和浏览器都不可用时,回退到本地缓存:
首选:Ethan 内置知识库(结构化存储、knowledge_search 可直接检索,比手维护 txt 可靠):
knowledge_add(title="flomo 标签索引", content="<标签列表,每行一个>", tags=["flomo","tags"])knowledge_search(query="flomo 标签") 找到条目后,按返回的 source 取全文。备选:纯文本文件 ~/.ethan/.cache/flomo-tags.txt(可靠性较低,仅当知识库不可用):
file_read 确认标签是否已存在;用到新标签后追加(去重)。flomo.py 脚本封装的私有 API(https://flomoapp.com/api/v1)通过从客户端本地存储提取 access_token + 请求签名(MD5)绕过了浏览器的 CORS 和 httpOnly cookie 限制。历史上纯浏览器路径存在是因为页面脚本无法读取登录态 token,但 Python 脚本从客户端文件系统直接提取 token,不受此限制。
手动 curl 仅在脚本和浏览器都不可用时考虑,且需自行处理签名(参考 flomo.py 的 _sign_params 函数)。
activate_tools: shell, file_read, file_write, browser_page, browser_tab, browser_session, set_secret, get_secret, knowledge_add, knowledge_search