一次配置,同步到七个 AI CLI【ClaudeCode、Codex、OpenCode...】

356 阅读4分钟

一次配置,同步到七个 AI CLI

我平时 Claude Code、Codex、OpenCode 挨个换着用。

问题是:加了一个 MCP,写了一个 Skill,只在其中一家生效。 想让其余几家也用上,就得手动复制目录、改格式、调路径——而且每家改法都不一样。

程序员天生就是 "懒惰的",所以现在这事变成一条命令:

npm i -g @jl-org/ai-sync
ai-sync
? 选择要迁移到的工具(方向键导航,空格选择,回车确认):
 ◯  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 铺出去:

~/.claude/commands/     自定义命令
~/.claude/skills/       技能
~/.claude/agents/       子代理
~/.claude/CLAUDE.md     全局指令
~/.claude.json          MCP

源码:github.com/beixiyo/ai-…


为什么不能直接 cp -r 复制粘贴?

一开始我以为就是复制文件加改后缀,因为 SKILL 其实就是这么通用的。

直到我看了每一家的配置文档才知道,这是有损转换——每一步都要在「转不过去」的地方替用户做决定。

下面三关是最麻烦的

第一关:MCP,每家配置基本都不一样

以为 MCP 是标准协议就万事大吉?配置格式每个厂商都不一样

同一个 filesystem server,Claude 写:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
    }
  }
}

OpenCode 要求:根字段叫 mcp 不叫 mcpServers,得加 typecommand 是数组、把 args 拍平进去,还要 enabled

{
  "mcp": {
    "filesystem": {
      "type": "local",
      "command": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/path"],
      "enabled": true
    }
  }
}

Codex 更彻底,直接是 TOML,根字段 mcp_servers

环境变量引用也各写各的。Claude 的 ${GITHUB_TOKEN} 到了 OpenCode 得写成 {env:GITHUB_TOKEN}

${GITHUB_TOKEN}
      ↓
{env:GITHUB_TOKEN}

这一关没什么技术含量,纯粹是读遍各家文档然后一个一个对。枯燥,但躲不掉。

第二关:Codex 的 TOML 装不下环境变量

这里是真难住我了

Codex 的 MCP 配置里,args 是一个静态字符串数组。它不做变量展开。

那 Claude 这条配置怎么办?

{
  "args": ["--token", "${GITHUB_TOKEN}"]
}

原样搬过去,Codex 会把 ${GITHUB_TOKEN} 这九个字符当成 token 发出去。

Codex 只提供了 env_vars(透传环境变量给子进程),可 token 在 参数 里,不在环境变量里。透传了也没用。

最后的解法是:参数里出现变量引用时,不直接执行,改成套一层 shell 让它自己展开。(注意,仅仅支持 Unix-Like 系统,Windows 要用 Git-Bash)

# 原本
command = "npx"
args = ["--token", "${GITHUB_TOKEN}"]

# 转换后
command = "sh"
args = ["-lc", "exec 'npx' '--token' \"$GITHUB_TOKEN\""]
env_vars = ["GITHUB_TOKEN"]

exec 是为了不多留一层 shell 进程。引号是重点:没有变量的参数用单引号锁死,有变量的才用双引号放行展开,不然用户参数里带个 $ 或反引号就出事了。

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 能写一堆东西——toolsmodel、权限之类的。这些字段换一家就不认识了。

我的处理是只保留最小公约数

function extractUniversalMetadata(metadata) {
  const result = {}
  if (metadata.name) result.name = metadata.name
  if (metadata.description) result.description = metadata.description
  return result
}

namedescription,就这两个。其余全丢。

丢的时候有个细节:如果丢完什么都不剩,就别留一个空的 frontmatter,直接输出正文。不然目标工具解析到一个空的 ---\n--- 反而可能报错。

OpenCode 还要特殊照顾一下,它靠 mode: subagent 才知道这是个子代理,得给它补上:

---                              ---
name: code-search        →       name: code-search
tools: [Read, Grep]              description: 代码搜索专家
model: sonnet                    mode: subagent
description: 代码搜索专家        ---
---

丢掉 toolsmodel 意味着: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-toolsargument-hintcontext 直接剥掉。

Gemini CLI 和 IFlow CLI 官方已经废弃了,这部分代码属于「还在,但不会主动跟进新特性」的状态。


如何自定义?

如果内置转换层不够用,写个 ai-sync.config.js 就能加:

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),各家改得挺勤,以官方文档为准

工具CommandsInstructionsMCP
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 字段差异:

工具根字段LocalRemote
Claude CodemcpServerscommand + argsurl + type
Codexmcp_serverscommand + args + env_varsurl + bearer_token_env_var
OpenCodemcptype: "local" + command[]type: "remote" + url
CursormcpServerscommand + argsurl
Gemini / IFlowmcpServerscommand + args + envhttpUrl + type

代码

源码:github.com/beixiyo/ai-… npm:npm i -g @jl-org/ai-sync

转换逻辑集中在这两个文件,想看细节直接翻: