一次配置,同步到七个 AI CLI
我平时 Claude Code、Codex、OpenCode 挨个换着用。
问题是:加了一个 MCP,写了一个 Skill,只在其中一家生效。 想让其余几家也用上,就得手动复制目录、改格式、调路径------而且每家改法都不一样。
程序员天生就是 "懒惰的",所以现在这事变成一条命令:
bash
npm i -g @jl-org/ai-sync
ai-sync
bash
? 选择要迁移到的工具(方向键导航,空格选择,回车确认):
◯ Cursor
⬤ Claude Code
⬤ OpenCode
◯ Gemini CLI
◯ IFlow CLI
◯ Codex
◯ CodeBuddy
? 配置到当前项目(否则为全局配置)? (y/N) n
? 是否自动覆盖已存在的文件? (y/N) y
开始迁移...
✓ 迁移 Commands... (2/2)
✓ 迁移 Skills... (1/1)
✓ 迁移 Instructions... (1/1)
✓ 迁移 MCP... (1/1)
✓ 迁移 Agents... (1/1)
--- 迁移完成 ---
工具: Claude Code, OpenCode
成功: 15
跳过: 3
错误: 0
~/.claude 是唯一的配置源,改它,然后 ai-sync 铺出去:
bash
~/.claude/commands/ 自定义命令
~/.claude/skills/ 技能
~/.claude/agents/ 子代理
~/.claude/CLAUDE.md 全局指令
~/.claude.json MCP
为什么不能直接 cp -r 复制粘贴?
一开始我以为就是复制文件加改后缀,因为 SKILL 其实就是这么通用的。
直到我看了每一家的配置文档才知道,这是有损转换------每一步都要在「转不过去」的地方替用户做决定。
下面三关是最麻烦的
第一关:MCP,每家配置基本都不一样
以为 MCP 是标准协议就万事大吉?配置格式每个厂商都不一样
同一个 filesystem server,Claude 写:
json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
}
}
}
OpenCode 要求:根字段叫 mcp 不叫 mcpServers,得加 type,command 是数组、把 args 拍平进去 ,还要 enabled:
jsonc
{
"mcp": {
"filesystem": {
"type": "local",
"command": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/path"],
"enabled": true
}
}
}
Codex 更彻底,直接是 TOML,根字段 mcp_servers。
环境变量引用也各写各的。Claude 的 ${GITHUB_TOKEN} 到了 OpenCode 得写成 {env:GITHUB_TOKEN}:
bash
${GITHUB_TOKEN}
↓
{env:GITHUB_TOKEN}
这一关没什么技术含量,纯粹是读遍各家文档然后一个一个对。枯燥,但躲不掉。
第二关:Codex 的 TOML 装不下环境变量
这里是真难住我了
Codex 的 MCP 配置里,args 是一个静态字符串数组。它不做变量展开。
那 Claude 这条配置怎么办?
json
{
"args": ["--token", "${GITHUB_TOKEN}"]
}
原样搬过去,Codex 会把 ${GITHUB_TOKEN} 这九个字符当成 token 发出去。
Codex 只提供了 env_vars(透传环境变量给子进程),可 token 在 参数 里,不在环境变量里。透传了也没用。
最后的解法是:参数里出现变量引用时,不直接执行,改成套一层 shell 让它自己展开。(注意,仅仅支持 Unix-Like 系统,Windows 要用 Git-Bash)
toml
# 原本
command = "npx"
args = ["--token", "${GITHUB_TOKEN}"]
# 转换后
command = "sh"
args = ["-lc", "exec 'npx' '--token' \"$GITHUB_TOKEN\""]
env_vars = ["GITHUB_TOKEN"]
exec 是为了不多留一层 shell 进程。引号是重点:没有变量的参数用单引号锁死,有变量的才用双引号放行展开 ,不然用户参数里带个 $ 或反引号就出事了。
js
function shellQuote(value) {
if (hasShellEnvRef(value))
return `"${escapeDoubleQuotedShellArg(value)}"`
return `'${value.replaceAll(`'`, `'"'"'`)}'`
}
顺带一提,转到 Codex 时我给每个 server 加了 default_tools_approval_mode = "approve"(免确认执行,你都配置了 MCP 了难道还要挨个问你要不要用吗?我认为多此一举)。
这是我替你做的决定,不喜欢的话转完手动改掉。
第三关:Agents 的 frontmatter,各家只认自己那套
Claude 的 agent frontmatter 能写一堆东西------tools、model、权限之类的。这些字段换一家就不认识了。
我的处理是只保留最小公约数:
js
function extractUniversalMetadata(metadata) {
const result = {}
if (metadata.name) result.name = metadata.name
if (metadata.description) result.description = metadata.description
return result
}
name 和 description,就这两个。其余全丢。
丢的时候有个细节:如果丢完什么都不剩,就别留一个空的 frontmatter ,直接输出正文。不然目标工具解析到一个空的 ---\n--- 反而可能报错。
OpenCode 还要特殊照顾一下,它靠 mode: subagent 才知道这是个子代理,得给它补上:
markdown
--- ---
name: code-search → name: code-search
tools: [Read, Grep] description: 代码搜索专家
model: sonnet mode: subagent
description: 代码搜索专家 ---
---
丢掉 tools 和 model 意味着:agent 的权限约束和模型指定都没了。 各家表达方式差太远,硬转不如不转,转完自己在目标工具那边补。
顺带还支持这几家
我自己日常就三个:Claude Code、Codex、OpenCode。剩下几家是顺手做的,配置照样能同步:
Cursor / CodeBuddy ------ Commands 和 Skills 都是 Markdown,基本直接复制,MCP 走 JSON 转换。
Gemini CLI / IFlow CLI ------ 这两家的 Commands 是 TOML,所以要走一遍 Markdown → TOML:frontmatter 转成 TOML 键,参数语法 $ARGUMENTS / $1 换成 {{args}} / {{arg1}},Claude 独有的 allowed-tools、argument-hint、context 直接剥掉。
Gemini CLI 和 IFlow CLI 官方已经废弃了,这部分代码属于「还在,但不会主动跟进新特性」的状态。
如何自定义?
如果内置转换层不够用,写个 ai-sync.config.js 就能加:
ts
import { defineConfig } from '@jl-org/ai-sync'
export default defineConfig({
tools: {
'test-cli': {
name: 'Test CLI',
supported: ['commands', 'skills', 'mcp'],
commands: {
source: '.claude/commands',
format: 'markdown',
target: '~/.test-cli/commands',
},
},
},
})
也可以只覆盖某个内置工具的某一项,比如把 Codex 的 prompts 换个位置。
不支持什么配置呢?
Rules 不同步。 带文件作用域的 Rules 系统只有 Cursor 和 Claude Code 有,其余五家没有,转过去也没地方放。想要目录级作用域,在子目录扔 AGENTS.md(比如 src/api/AGENTS.md)
大部分工具都认这个,Codex 的 Rule 配置甚至直接就叫做 AGENTS.md
Hooks 不同步。 不是懒。Claude Code / Codex / Cursor 是 JSON 配置 + Shell 命令,OpenCode 是 TypeScript 模块,连「hook 在什么时机触发、拿到什么参数、怎么表达拒绝」都对不上。硬转出来是个跑不起来的壳子,不如不转。
单向,不做双向合并。 ~/.claude 是唯一的真相来源,目标端的配置该覆盖就覆盖(会问你)。双向 merge 要处理冲突、要记录来源,复杂度翻好几倍,我暂时不打算做。
各工具配置位置对照
以下路径来自 ai-sync 内置配置(src/lib/configs),各家改得挺勤,以官方文档为准
| 工具 | Commands | Instructions | MCP |
|---|---|---|---|
| Claude Code | ~/.claude/commands/ |
~/.claude/CLAUDE.md |
~/.claude.json |
| Codex | ~/.codex/prompts/ |
~/.codex/AGENTS.md |
~/.codex/config.toml |
| OpenCode | ~/.config/opencode/commands/ |
~/.config/opencode/AGENTS.md |
~/.config/opencode/opencode.jsonc |
| Cursor | ~/.cursor/commands/ |
~/.cursor/AGENTS.md |
~/.cursor/mcp.json |
| CodeBuddy | ~/.codebuddy/commands/ |
~/.codebuddy/CODEBUDDY.md |
~/.codebuddy/.mcp.json |
| Gemini CLI | ~/.gemini/commands/(TOML) |
~/.gemini/GEMINI.md |
~/.gemini/settings.json |
| IFlow CLI | ~/.iflow/commands/(TOML) |
~/.iflow/IFLOW.md |
~/.iflow/settings.json |
MCP 字段差异:
| 工具 | 根字段 | Local | Remote |
|---|---|---|---|
| Claude Code | mcpServers |
command + args |
url + type |
| Codex | mcp_servers |
command + args + env_vars |
url + bearer_token_env_var |
| OpenCode | mcp |
type: "local" + command[] |
type: "remote" + url |
| Cursor | mcpServers |
command + args |
url |
| Gemini / IFlow | mcpServers |
command + args + env |
httpUrl + type |
代码
源码:github.com/beixiyo/ai-... npm:
npm i -g @jl-org/ai-sync
转换逻辑集中在这两个文件,想看细节直接翻:
- converters/mcp.ts ------ 各家 MCP 格式互转,第一、二关的代码
- converters/agent.ts ------ Agent frontmatter 处理,第三关的代码
- converters/markdown-to-toml.ts ------ Commands 转 TOML(Gemini / IFlow)