首页
/ claude-mem Cowork 插件配对实战:mem-setup 技能如何把 cmem.ai 凭据安全接入云会话

claude-mem Cowork 插件配对实战:mem-setup 技能如何把 cmem.ai 凭据安全接入云会话

2026-09-06 15:18:45作者:齐添朝

本篇以 mem-setup 技能 为核心,讲解 claude-mem 的 Cowork 插件(claude-mem-cowork)如何完成"配对"(pairing):从 cmem.ai → Connect 获取 sync token、user id、SyncHub URL 三个值,写入插件配置,使 Hooks 能够在 Claude 官方 App 的云端会话中持续捕获并注入记忆。读完你可以独立完成一次完整的 Cowork 配对、理解凭据的多级回退解析机制,并掌握不泄露密钥的验证方法。

为什么 Cowork 需要单独的配对流程

本地 claude-mem 的模型是"用户机器上跑一个 worker 服务 + 本地 SQLite"。而 Cowork 会话运行在临时(ephemeral)云端容器里,本地 worker/SQLite 无法在那里持久化。Cowork 插件的做法是用一组"薄 HTTPS 垫片"替代 worker:Hooks 捕获工具调用并把原始片段流式上传到 cmem.ai,由云端 Pro 服务端运行 worker 和 observer;编译好的观察结果再被注入回每个新会话和每个派生的子 agent(见 cowork/README.md)。

正因为 worker 在云端,插件本身必须持有一组每用户的凭据才能工作。mem-setup 技能的核心定位就是:把用户自己在 cmem.ai → Connect 页面上看到的三个值配置进插件——"任何人都可以配对,凭据是每用户的配置,绝不硬编码在插件逻辑里"(原文档开篇即强调这一点)。

插件的钩子注册见 hooks/hooks.json,所有事件最终都落到同一个垫片脚本 scripts/cmem-hook.mjs 上,例如 SessionStart 触发 node "${CLAUDE_PLUGIN_ROOT}/scripts/cmem-hook.mjs" contextPostToolUse(matcher 为 *)触发 observation 事件。配对写好的凭据正是这些钩子发请求时的 Bearer API key——没有它,整个插件按设计静默空转(fail-soft)。

第一步:从 cmem.ai → Connect 收集三个值

原文档明确规定需要向用户收集的值(cmem.ai → Connect 页面提供):

  1. sync token(以 cm_ 开头)——作为 Bearer API key 使用;
  2. user id(UUID);
  3. SyncHub URL(一个 workers.dev 或 cmem.ai 的 URL)。

操作要点:

  • 如果用户直接粘贴了整段 Connect 说明文本,要从中提取出这三个值,而不是让用户逐个再报一遍;
  • 如果三者不全,至少要拿到 sync token——另外两个是可选的。

这三个值与源码中的配置字段是一一对应的。查看 config.json 的默认结构:

{
  "apiBase": "https://cmem.ai",
  "apiKey": "",
  "userId": "",
  "syncHubUrl": "",
  "inject": {
    "sessionStart": true,
    "agents": true,
    "maxChars": 6000
  },
  "capture": {
    "skipTools": []
  }
}
  • apiKey 对应 sync token(cm_...),是必需的;
  • userId 对应 Connect 页面的 UUID;
  • syncHubUrl 对应 SyncHub URL(workers.dev 或 cmem.ai 域名);
  • apiBase 是云端 API 根地址,默认 https://cmem.ai,一般无需改。

密钥处理红线:不打印、不进 argv、不写日志

原文档把密钥处理列为"non-negotiable(不可妥协)",共两条铁律:

  • 绝不在对话里回显 token不把它放进 shell argv不写进日志
  • 只通过文件写(Write/Edit 工具)和文件读来搬动它

这条纪律直接映射到钩子脚本的安全设计里。cmem-hook.mjs 顶部的注释写明:失败重试用的 spool 文件放在 ~/.claude-mem 且权限 0600,"绝不用全局可读的临时目录(payload 里可能含工具输出)"。也就是说,token 一旦落盘就只以 config.json / settings.json 的文件形式存在,钩子进程运行时再从文件读入内存,任何 stdout/stderr 路径都不会吐出完整密钥——后面"验证"一节里的 status 命令也只回显 token 的最后 4 位。

第二步:写入插件 config.json

原文档第 1 步的操作:定位插件根目录(即 mem-setup 技能所在的这个插件本身),更新它的 config.json

  • apiKey ← sync token
  • userId ← user id
  • syncHubUrl ← SyncHub URL
  • 用户不要求就不动其他设置——inject 是开关类选项(sessionStartagentsmaxChars,默认全开、注入块上限 6000 字符);
  • 项目命名是自动的(cmem_work_* 前缀),不可配置。这一点在源码里也被刻意固死:cmem-hook.mjsresolveProject() 从工作目录推导项目名,根目录会话落到 cmem_work_root,项目文件夹会话得到 cmem_work_<文件夹slug>roothomeworkspace 等通用目录名统一归为 root)。注释解释原因:claude-mem 是整个生态里更大的系统,这里手动覆盖命名会造成分叉、破坏下游一致性,所以"故意不设成可选项"。

从源码结构看,写入 config.json 之所以是"唯一持久的写入目标",是因为凭证解析顺序中它排在环境变量之后、本地 settings 之前——loadConfig() 的合并逻辑如下(cmem-hook.mjs 第 50–55 行):

const pick = (...vals) => vals.find(v => typeof v === 'string' && v.trim()) || '';
const cfg = {
  apiBase:   pick(process.env.CMEM_API_BASE, file.apiBase, local.apiBase) || 'https://cmem.ai',
  apiKey:    pick(process.env.CMEM_API_KEY, file.apiKey,
                  local.CLAUDE_MEM_CLOUD_SYNC_TOKEN, local.syncToken, local.apiKey, local.token),
  userId:    pick(process.env.CMEM_USER_ID, file.userId,
                  local.CLAUDE_MEM_CLOUD_SYNC_USER_ID, local.userId),
  syncHubUrl: pick(process.env.CMEM_SYNC_HUB_URL, file.syncHubUrl,
                   local.CLAUDE_MEM_CLOUD_SYNC_HUB_URL, local.syncHubUrl, local.hubUrl),
  ...
};

即每个字段都是"环境变量 → 插件 config.json → ~/.claude-mem/settings.json"三级回退,且兼容旧版短键名(syncTokenhubUrl 等)。这解释了原文档的推荐顺序:Cowork 容器里改 config.json 是主路径;env 变量只在能控制容器环境时使用;本地 settings 文件是与本地安装的兼容通道。

第三步:理解临时容器的局限,必要时重新打包

原文档第 2 步点出了 Cowork 配对的一个关键陷阱:Cowork 容器是临时的,对已安装副本的编辑只在本会话有效。要让配对永久生效,需要重新打包——把插件目录压缩为 <plugin-name>.plugin 发送给用户重新安装(原文档称之为 "the cowork-plugin skill's packaging flow"),并明确告诉用户原因。

README.md 的"Fail-soft guarantees"一节可以印证临时性带来的行为边界:

  • 未配置 API key → 所有钩子都是静默 no-op,会话永不中断(ingest()if (!CFG.apiKey) return; 即此逻辑);
  • cmem.ai 不可达 → 捕获事件暂存到 spool 文件,后续钩子触发时批量 flush 到 /api/hooks/ingest
  • 钩子端点尚未部署 → 上下文注入自动回退到 /api/mcp 的实时 memory_search
  • 每个钩子无条件以退出码 0 结束——脚本 main 入口的 try { ... } catch { /* rule #1: never break the session */ } process.exit(0); 就是这条"设计规则 #1"的实现。

也就是说:即便你忘了重打包,配对失效的表现是"记忆功能悄悄关闭",而不是会话报错——排查时应优先检查配置是否真的写进去了。

第四步(可选):同步到本地 claude-mem 的 settings.json

如果这台机器同时装有本地 claude-mem(判断标准:存在 ~/.claude-mem/ 目录),原文档第 3 步建议可选地把同样的三个值写入 ~/.claude-mem/settings.json,且文件权限设为 0600。使用的键名与本地 claude-mem 的 cloud-sync 配对完全一致:

{
  "CLAUDE_MEM_CLOUD_SYNC_TOKEN": "cm_…",
  "CLAUDE_MEM_CLOUD_SYNC_USER_ID": "<UUID>",
  "CLAUDE_MEM_CLOUD_SYNC_HUB_URL": "https://sync.cmem.ai"
}

这三个键在本地主代码中同样是一等公民:src/shared/SettingsDefaultsManager.ts 中定义了 CLAUDE_MEM_CLOUD_SYNC_TOKENCLAUDE_MEM_CLOUD_SYNC_USER_IDCLAUDE_MEM_CLOUD_SYNC_HUB_URL 三个字段,默认值全部为空字符串,并注释"Empty = sync OFF"(Hub URL 示例为 https://sync.cmem.ai)。Cowork 钩子脚本读取同一文件(local.CLAUDE_MEM_CLOUD_SYNC_*),目的正如脚本注释所说:"让同一套凭据同时服务云端和本地两个世界",本地 worker 与钩子脚本都会读它。

0600 权限不是建议性的:settings 里含 sync token,spool 文件同理(appendFileSync(..., { mode: 0o600 }))。在共享机器上这是硬性要求。

第五步:不泄露密钥的验证

原文档第 4 步给出的验证命令:

node "${CLAUDE_PLUGIN_ROOT}/scripts/cmem-hook.mjs" status

执行后报告脱敏后的输出status 子命令的实现(cmem-hook.mjscliStatus())会打印五项:

输出行 含义
api base 生效的 API 根地址(含 env 覆盖结果)
project 自动推导的项目名(cmem_work_*
api key configured (…xxxx) 仅显示后 4 位;MISSING 表示配置没写进去
/api/hooks/context Pro 端点的 HTTP 状态;404 属于预期——Pro 端点部署完成前会如此,搜索/注入仍走 /api/mcp
/api/mcp memory_search 实时检索通道是否可用(OK / unavailable
spool 是否有积压事件(pending events at ... / empty

原文档对两种典型结果的解释值得原样记住:MISSING 说明上一步的文件写入没生效(重查 config.json 是否真的落地、是否被容器重置);/api/hooks/context 返回 404 不是故障,云端钩子端点尚未上线时的预期状态,插件会自动回退到 /api/mcpmemory_search 通道完成注入与检索。这一回退链在 PRO-ENDPOINT-SPEC.md 里也被明确为"client fallback to /api/mcp (already live)"。

备选通道:纯环境变量,零文件编辑

原文档最后给出的"Alternate source":环境变量覆盖一切,且完全不需要改文件

CMEM_API_KEY        # sync token(必需,最小集合)
CMEM_USER_ID        # user id(可选)
CMEM_SYNC_HUB_URL   # SyncHub URL(可选)
CMEM_API_BASE       # API 根地址,默认 https://cmem.ai(可选)

loadConfig()pick() 实现可以确认优先级:env 变量 > config.json > ~/.claude-mem/settings.json。适合能注入容器的托管环境;而在普通 Cowork 会话里,写入 config.json 才是"持久选项"(配合重新打包)。

配套能力:mem-search 技能与搜索纪律

配对完成后,同一插件还提供 mem-search 技能,用于在新会话里主动检索 cmem.ai 上的历史记忆(跨 Claude Code、Cowork、Codex 等所有 agent 的时间戳观察):

node "${CLAUDE_PLUGIN_ROOT}/scripts/cmem-hook.mjs" search "your query" --limit 20

检索遵循 claude-mem 的 Index → Timeline → Transcript 渐进式纪律:先用 1–3 次宽泛关键词搜索扫标题/摘要,再用第一步命中的最具体词(ID、精确短语)加更小的 --limit 收窄,最后基于命中的观察作答——用户问"什么时候"时引用时间戳。该技能同样强调两条安全规则:不要把密钥写进搜索词(查询会发送到 cmem.ai);不要向用户倾倒原始输出,只提炼相关观察。搜索无结果时用同一条 status 命令诊断。

隐私与数据卫生:捕获前发生了什么

虽然 mem-setup 技能本身只讲"配对",但配对的直接后果就是插件开始向 cmem.ai 流式上传工具使用片段。结合 README.md 的 Privacy notes 与钩子脚本实现,理解以下几点能让配对决策更踏实:

  • 截断tool_input / tool_response 每字段发送前截断到 16 KB(FIELD_CAP = 16000),用户/agent prompt 截断到 4 KB(PROMPT_CAP = 4000);
  • 脱敏:上传前对 payload 做单遍线性正则脱敏——PEM 私钥块、JWT、OpenAI/Anthropic 风格 key(sk-/sk-ant-)、GitHub/GitLab token、Stripe 风格 key、Slack token、AWS key ID、Google API key、npm token、连接串 userinfo(postgres://user:pass@host 中的 user:pass)、Cookie/Authorization 头值、password:/api_key= 式键值对,全部替换为 [cmem-redacted]ssh://git@github.com 这类无密码冒号的 userinfo 会保留,因为那是信号不是秘密;
  • 隐私标签<private>…</private> 区域整体剥离,且剥离发生在截断之前——避免截断把闭合标签切掉后泄露半个私有区域;
  • 反馈环保护mcp__memory__*mcp__cmem* 自身的调用永不捕获(正则 ALWAYS_SKIP),用户可在 capture.skipTools 中追加更多排除项。

这些行为的正确性由 cowork/test/run-tests.mjs 的测试矩阵验证,包括:observation 正确 POST 到 /api/hooks/ingest 且携带 Bearer 认证、10 万字符的响应被截断到 16 KB 左右、sk-ant- key / GitHub token / key=value 秘密 / 连接串密码被红掉而 https://api.example.comAPI_KEY= 等信号保留、含 /@ 的密码仍被完整覆盖等。

小结:配对的完整检查清单

按 mem-setup 技能的原始步骤,一次完整的 Cowork 配对可以浓缩为:

  1. 从 cmem.ai → Connect 拿到 cm_ token、UUID、SyncHub URL(缺啥都要 token);
  2. 全程不回显、不进 argv,只用文件工具写入;
  3. 更新插件 config.jsonapiKey / userId / syncHubUrl,不动 inject 等其他项;
  4. 意识到容器临时性:需要持久就重打包为 <plugin-name>.plugin 让用户重装;
  5. 有本地 claude-mem 时,可选写入 ~/.claude-mem/settings.json(0600),复用 CLAUDE_MEM_CLOUD_SYNC_* 键;
  6. node "${CLAUDE_PLUGIN_ROOT}/scripts/cmem-hook.mjs" status 验证:MISSING 查文件,404 属预期,spool 为空即健康。

至此,插件的七个钩子(SessionStart / UserPromptSubmit / PostToolUse / PreToolUse on Task|Agent / SubagentStop / Stop / SessionEnd,见 hooks/hooks.json)开始以用户的身份向 cmem.ai 捕获并注入记忆,Cowork 云会话与本地 Claude Code 会话共享同一份跨平台记忆。

登录后查看全文
热门项目推荐
相关项目推荐