`ccswitch://` 一键导入:四类载荷与解析器的边界

2026-08-10

ccswitch:// 是 CC Switch 注册的自定义协议,用来把一份供应商配置、一段提示词、一组 MCP 服务器或一个 Skill 仓库坐标从链接里导进本机。听上去像”一个协议四种用法”,但真去读 src-tauri/src/deeplink/parser.rs 会发现:骨架是共用的,四类载荷的参数契约却完全不对称——同一个”指定应用”的语义,在四类里有三种不同写法,其中一类干脆不让你指定。

以下全部基于我们本地 clone 的 cc-switch 仓库快照 c39c903(提交日期 2026-08-10),仓库内版本号 3.19.2。我们只读源码文本与 docs/ 下的用户手册,没有安装也没有运行过这个桌面应用,所以本文只讲解析器的代码语义,不涉及任何界面与实际运行表现。

骨架:四道校验,缺一不可

parse_deeplink_url 在分发到具体解析器之前,先连着做四件事(src-tauri/src/deeplink/parser.rs:20-56):

  1. scheme 必须是 ccswitch,其它 scheme 直接判失败;
  2. 版本取自 URL 的 host 段,必须恰好等于 v1,不是 v1 就过不去;
  3. path 必须恰好等于 /import
  4. query 里必须有 resource,缺了直接失败。

第二条最容易被误读。ccswitch://v1/import?... 里的 v1 不是路径的一部分,它在 URL 语法里占的是 host 的位置,所以你把它写成 ccswitch:///v1/import 或者 ccswitch://import/... 都过不去——前者 host 为空,后者 host 是 import 而不是 v1。仓库里有两个测试正对着这两道校验:test_parse_invalid_schemesrc-tauri/src/deeplink/tests.rs:124)与 test_parse_unsupported_versiontests.rs:133)。

第四步之后是一个 match,resource 只接受 provider / prompt / mcp / skill 四个值,其余一律 “Unsupported resource type”(parser.rs:59-66)。四类载荷从这里开始各走各的。

反直觉的那一处:三种「指定应用」的写法

如果你把这四类当成”同一张表填不同的字段”,第一个坑就在这里。“这条链接要导给哪个 CLI”这件事,四类载荷用了三种完全不同的表达方式:

resource指定应用的参数取值范围依据
providerapp(必填)7 项:claude、codex、gemini、grokbuild、opencode、openclaw、hermesparser.rs:82-89
promptapp(必填)同上 7 项parser.rs:191-198
mcpapps(必填,复数)7 个可选值,额外接受别名 grok(等价 grokbuild)parser.rs:261-273
skill无此参数解析结果里 app 被硬编码为 "claude"parser.rs:349

这张表的读法是:前两行的参数名是单数 app,第三行是复数 apps,第四行根本不接受skill 那一行的源码注释写得很直白:“Skills are Claude-only”。也就是说,你手搓一条 skill 深链再挂个 app=codex 上去,这个参数不会报错,它只是被忽略掉——最终落库的 appclaude

mcp 那个别名也值得单独记一笔:只有 apps 这一处额外认 grok 这个写法,providerprompt 的白名单里没有它。同一个应用在同一份解析器的不同分支里,一处认两种拼法、两处只认一种。这类差异我们只陈述,不推断原因。

顺带说清各类的必填项,省得你对着报错猜:provider 必填 appnamehomepage / endpoint / apiKey 可选,注释注明可选项是为 v3.8+ 的配置文件自动填充准备的(parser.rs:76-99);prompt 必填 appnamecontentparser.rs:185-210);mcp 必填 appsconfigparser.rs:255-293);skill 只必填 repodirectorybranch 可选(parser.rs:327-340)。

repo 的格式校验也比”含斜杠即可”严一档:必须是恰好两段owner/namea/b/c 这种三段写法会在格式校验这一步被挡掉(parser.rs:327-340)。

endpoint 可以是多个,config 必须先 Base64

第二处容易踩空的是编码约定。

endpoint 支持逗号分隔的多个 URL,解析时逐个过 validate_url,报错信息会标成 endpoint[i] 的形式告诉你是第几个坏了(parser.rs:107-115)。validate_url 本身只放行 http 与 https 两种 scheme(src-tauri/src/deeplink/utils.rs:10-22)。这三种写法各有对应测试:test_parse_multiple_endpoints_comma_separatedtest_parse_single_endpoint_backward_compatibletest_parse_endpoints_with_spaces_trimmedtests.rs:942956969),最后一个说明逗号两侧的空格会被 trim 掉。

编码这一层的关键在两个参数上:

  • mcpconfig:先 Base64 解码 → 转 UTF-8 → 解析 JSON → 解出来的对象必须含 mcpServers,为空则报 “No MCP servers found in config”(src-tauri/src/deeplink/mcp.rs:62-89)。另外 config_format 在解析器里被硬编码成 "json",注释是 “MCP config is always JSON”(parser.rs:255-293),你传别的值不起作用。
  • promptcontent:走 decode_base64_param,结构体字段注释写明是 “Base64 encoded Markdown content”(src-tauri/src/deeplink/prompt.rs:47src-tauri/src/deeplink/mod.rs:83-85)。

Base64 这一步有一层容错,写在 utils.rs:24-74:解码前会尝试把空格还原成 +(URL 传输中 + 常被当成空格)、补齐 = padding,然后在 STANDARD / STANDARD_NO_PAD / URL_SAFE / URL_SAFE_NO_PAD 四种引擎之间轮询。对应测试是 test_import_prompt_allows_space_in_base64_contenttests.rs:822)。这层容错针对的就是 + 被转成空格、padding 被截掉这两种情形(utils.rs:24-74)。

还有一个小机制:infer_homepage_from_endpoint 会把 host 的 api.api- 前缀去掉再拼成 https://{host},用于链接没带 homepage 时的补齐(utils.rs:88-98),测试见 test_infer_homepagetest_infer_homepage_from_endpoint_without_homepagetests.rs:178979)。

一条链接可以带脚本,但带上不等于开启

provider 类深链可以携带用量查询脚本。默认行为写在 src-tauri/src/deeplink/mod.rs:117-120 的字段注释里,原文是:carrying a script is not itself a decision to run it; the link must say usageEnabled=true——usage_enabled 默认禁用,链接里必须显式写 usageEnabled=true 才会启用

这条约定在 tests.rs 里有 5 个专门的测试守着(tests.rs:297382406427475),从函数名就能看出各自守什么:不把 provider 的凭据复制给用量脚本、不因为携带了代码就视为启用、显式请求时才照办、与 provider 相同的凭据会被省略、不同的用量凭据要保留。另有一个非测试的构造辅助函数 usage_script_requesttests.rs:346)。

这是本篇里唯一一处涉及”链接会不会让本机跑第三方代码”的设计,值得读者自己去 mod.rs:117-120 看一眼原文。同时必须说清楚:深链导入会把 API Key 一类内容写进本机配置,这是本机敏感数据,源码里的默认值只是默认配置,不构成任何”这样就安全”的保证。

用户手册与代码的四处口径差

docs/user-manual/zh/5-faq/5.3-deeplink.md3.2-prompts.md 里关于深链的描述,有四处与代码对不上。按纪律,我们只标出每一处在文档与代码里的具体位置,不推断哪一处”才算数”、也不据此评价项目:

  1. prompt 深链的整体格式3.2-prompts.md:152-154 写的是 ccswitch://import/prompt?data=<base64编码的预设>;代码要求 host 必须为 v1、path 必须为 /import,参数是 resource=prompt&app=&name=&content=没有 data 这个参数parser.rs:28-47180-213)。
  2. mcpconfig 编码5.3-deeplink.md:78-83:110config 描述为”MCP 服务器配置(JSON 格式)“,示例给的是 URL 编码的裸 JSON;代码要求先 Base64 解码,且解出的 JSON 必须含 mcpServers 对象,否则报 “MCP config must contain ‘mcpServers’ object”(deeplink/mcp.rs:68-83)。
  3. promptcontent 编码5.3-deeplink.md:70-74:116 只写”提示词内容”,示例是 URL 编码明文;代码走 decode_base64_paramdeeplink/prompt.rs:47)。
  4. app 白名单的长度5.3-deeplink.md:41 的通用参数表列 5 项(claude / codex / gemini / opencode / openclaw);代码接受 7 项,多出 grokbuild 与 hermes(parser.rs:82-89)。

说完就停。要用哪一份口径,以你自己 clone 到的仓库状态为准。

你可以怎么自己复核

这几条都能在本机分钟级验完,不需要装这个应用:

# 1. 看骨架四道校验
sed -n '20,66p' src-tauri/src/deeplink/parser.rs

# 2. 数 deeplink 模块的测试量
grep -c "#\[test\]" src-tauri/src/deeplink/tests.rs

# 3. 把手册那四处口径与代码并排看
grep -n "ccswitch://" docs/user-manual/zh/5-faq/5.3-deeplink.md

第二条的结果是 33tests.rs 本身 989 行;整个 src-tauri/src/deeplink/ 目录 8 个文件合计 3161 行,其中 provider.rs 一个就占 1182 行,skill.rs 只有 64 行(wc -l src-tauri/src/deeplink/*.rs,2026-08-10 采集)。测试文件占了模块近三分之一的行数,这个比例本身是个可核查的事实;但要注意我们只做了静态计数,没有运行过任何测试,不知道它们当前是否全绿。

另外 deeplink/mcp.rs 里还有一个单测 enabled_apps_merge_covers_every_supported_mcp_clientdeeplink/mcp.rs:205-227),断言合并之后 claude / codex / gemini / grokbuild / opencode / hermes 六项全为 true——注意这里是 6 项,与前面 apps 参数白名单的 7 个可选值不是同一个集合。

导进来之后落到哪,以及一处未完成标记

四类载荷的落点也不一样,这决定了”导入成功”到底意味着什么:

  • mcp:已存在的服务器只合并 apps 标志,其余字段保持原样;新建的服务器会打上 tags: ["imported"]deeplink/mcp.rs:99-128)。所以对已有条目做深链导入,不会覆盖你手动改过的命令与参数。
  • prompt:id 形如 {sanitized_name}-{timestamp_ms},name 只保留字母数字与 -/_ 并转小写;先以 enabled: false 落库,再按 enabled 参数决定要不要调 enable_promptdeeplink/prompt.rs:47-79)。
  • skill:这一类的落点最轻——只写 skill_repos 表,保存仓库坐标而已branch 缺省 mainenabled 缺省 true(deeplink/skill.rs:36-59)。写入前会先调 SkillService::validate_repo_ref 做一道纵深拦截,注释解释这样做是为了”让用户当场看到错误而不是让脏数据沉淀进 skill_repos 表”(deeplink/skill.rs:38-48)。
  • providerdeeplink/provider.rs 是四类里最长的一个文件(1182 行),我们这次只读到了 mod.rs 里的字段定义和其中一行未完成标记:// Fetch remote config (TODO: implement remote fetching in next phase)provider.rs:608)。这条远程拉取配置的路径在我们读到的快照里还带着 TODO,照实标出来。provider 导入与 parse_and_merge_config 的合并规则我们没有通读,因此本文不写。

把这几条并起来看,ccswitch:// 的边界其实相当克制:它负责的是”把一份配置的坐标搬进来”,而不是”替你把东西装好跑起来”——skill 只落一行仓库坐标,prompt 默认不启用,用量脚本必须显式开启。你要判断一条来路不明的深链会做什么,从 resource 值开始,照上面四组落点逐条对,就能知道它最多能改动哪张表。


本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册与发布说明、 src/config/ 的预设定义与 src-tauri/src/ 的后端源码整理,核对日 2026-08-10,对应仓库快照 c39c903。 本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用, 因此不涉及界面外观、操作手感与切换速度的任何描述。 文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。 该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。