Plugin Marketplace:不是记一个 URL,而是一套声明、物化与调和的三层逻辑
对应官方文档:claude-code-docs/docs/discover-plugins.md 里的 How marketplaces work / Add marketplaces。
核心概念
在许多工具中,“添加源”可能只是在配置文件里多写一行地址,等到需要下载时再临时访问。但 Claude Code 的 Marketplace 机制要复杂得多。
它本质上是一个本地 Catalog 物料管理层。添加 Marketplace 的过程不是“记录一个 URL”,而是:
- 物化 Catalog:将远端(GitHub/Git/NPM/URL)的清单克隆并“固化”到本地
~/.claude/plugins/marketplaces/下。 - 状态登记:在
known_marketplaces.json里登记这个 Marketplace 的本地路径和元数据。 - 持久化管理:通过一个“调和器 (Reconciler)”不断确保本地物料与用户的配置意图(Intent)保持同步。
这种设计的核心是让插件源变得离线可用、快速可查、且能被企业策略 (Policy) 精准控制。
源码级拆解
Claude Code 管理 Marketplace 的核心是 reconciler.ts 和 marketplaceManager.ts,分为四个关键步骤:
1. 意图声明与默认策略 (Intent Declaration)
getDeclaredMarketplaces() 会从 settings.json 的 extraKnownMarketplaces 中提取用户添加的源。 此外,它还包含一个隐藏逻辑:如果你在设置中启用了 @claude-plugins-official 作用域下的插件,系统会自动将官方的 Anthropic Marketplace 声明进来,无需手动执行 add。
2. 状态差异分析 (The Diffing)
src/utils/plugins/reconciler.ts 中的 diffMarketplaces(...) 是核心逻辑。它会拿“当前的配置意图 (Intent)”去对比“磁盘上的真实物化状态 (known_marketplaces.json)”,并将差异分为三类:
missing:设置里声明了,但本地还没物化。sourceChanged: Marketplace 名字相同,但源地址(比如从 HTTPS 改成了 SSH)已经变了。upToDate:本地状态与配置一致。
3. 后台物化调和 (Background Reconcile)
在 PluginInstallationManager.ts 中,系统会在后台异步运行 reconcileMarketplaces()。 它不是直接覆盖磁盘,而是基于 diff 结果按需补齐。特别是对于 missing 或 sourceChanged 的项,它会调用 addMarketplaceSource(...) 进行克隆或拉取。 这个过程是增量且幂等的。如果源地址没变,它绝不会浪费流量重新下载。
4. 自动更新与刷新策略 (Auto-Update Policy)
Marketplace 并不总是在启动时更新所有内容。pluginAutoupdate.ts 会检查 autoUpdate 标志:
- 官方源:默认
autoUpdate: true。 - 第三方源:默认
false(除非在 settings 中显式设为 true)。 这种区分策略在保证核心体验流畅的同时,也避免了因频繁拉取不可信源而产生的启动延迟或安全隐患。
实战注意事项
- Add Marketplace != Install Plugin:添加 Marketplace 只是把货架摆到了店里。货架上有什么(Catalog)你可以看到了,但要把货(Plugin)装进系统,还需要执行
plugin install。 - 意图 (Intent) 决定状态 (Materialization):如果你从
settings.json里删掉了一个 Marketplace 声明,它在known_marketplaces.json里的登记依然可能存在。Claude Code 的调和器目前主要是增量式 (Additive) 的,它不主动帮你删除那些已经失去声明的“流浪”Marketplace 物料。 - Policy 检查发生在克隆前:如果你添加了一个被企业 Policy 禁用的源,
marketplaceManager.ts会在发起任何网络请求前直接拦截它。 - Marketplace 刷新不代表插件热切:Catalog 更新成功后,当前会话的插件并不会自动变强。你可能需要收到提示后手动执行
/reload-plugins。
延伸阅读
- 如果你想看 Marketplace 里的条目是如何转换成具体的插件物料的,请看
plugin-caching-and-resolution.md。 - 如果你更关心插件版本是如何通过 Marketplace 里的 Commit SHA 或版本号确定的,请看
version-resolution.md。
源码锚点
claude-code-opensource/src/utils/plugins/marketplaceManager.ts
📄 src/utils/plugins/marketplaceManager.ts
type KnownMarketplace,
type KnownMarketplacesFile,
KnownMarketplacesFileSchema,
type MarketplaceSource,
type PluginMarketplace,
type PluginMarketplaceEntry,
PluginMarketplaceSchema,
validateOfficialNameSource,
} from './schemas.js'
/**
* Result of loading and caching a marketplace
*/
type LoadedPluginMarketplace = {Marketplace 的底层读写和 Catalog 缓存逻辑。
claude-code-opensource/src/utils/plugins/reconciler.ts
📄 src/utils/plugins/reconciler.ts
type KnownMarketplacesFile,
type MarketplaceSource,
} from './schemas.js'
export type MarketplaceDiff = {`diffMarketplaces` 和 `reconcileMarketplaces` 的实现,它是 Intent 到 Materialization 的核心流程。
claude-code-opensource/src/utils/plugins/pluginAutoupdate.ts
📄 src/utils/plugins/pluginAutoupdate.ts
export type PluginAutoUpdateCallback = (updatedPlugins: string[]) => void
// Store callback for plugin update notifications
let pluginUpdateCallback: PluginAutoUpdateCallback | null = null
// Store pending updates that occurred before callback was registered
// This handles the race condition where updates complete before REPL mounts
let pendingNotification: string[] | null = null
/**
* Register a callback to be notified when plugins are auto-updated.
* This is used by the REPL to show restart notifications.
*
* If plugins were already updated before the callback was registered,
* the callback will be invoked immediately with the pending updates.
*/
export function onPluginsAutoUpdated(
callback: PluginAutoUpdateCallback,
): () => void {
pluginUpdateCallback = callback
// If there are pending updates that happened before registration, deliver them now
if (pendingNotification !== null && pendingNotification.length > 0) {
callback(pendingNotification)
pendingNotification = null
}
return () => {
pluginUpdateCallback = null
}实现了 Marketplace 的自动刷新策略(官方 vs 第三方)。
claude-code-opensource/src/utils/plugins/officialMarketplace.ts
📄 src/utils/plugins/officialMarketplace.ts
export const OFFICIAL_MARKETPLACE_SOURCE = {
source: 'github',
repo: 'anthropics/claude-plugins-official',
} as const satisfies MarketplaceSource定义了官方 Marketplace 的预设源地址和保留名称规则。
claude-code-opensource/src/services/plugins/PluginInstallationManager.ts
📄 src/services/plugins/PluginInstallationManager.ts
type SetAppState = (f: (prevState: AppState) => AppState) => void
/**
* Update marketplace installation status in app state
*/
function updateMarketplaceStatus(
setAppState: SetAppState,
name: string,
status: 'pending' | 'installing' | 'installed' | 'failed',
error?: string,
): void {
setAppState(prevState => ({
...prevState,
plugins: {
...prevState.plugins,
installationStatus: {
...prevState.plugins.installationStatus,
marketplaces: prevState.plugins.installationStatus.marketplaces.map(
m => (m.name === name ? { ...m, status, error } : m),
),
},
},
}))
}将 Marketplace 的调和过程挂载到启动流程中的后台任务。