feat(ce-update): add plugin version check skill and ce_platforms filtering (#532)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import type { ClaudeAgent, ClaudeCommand, ClaudePlugin, ClaudeSkill } from "../types/claude"
|
||||
import { type ClaudeAgent, type ClaudeCommand, type ClaudePlugin, type ClaudeSkill, filterSkillsByPlatform } from "../types/claude"
|
||||
import type { CodexBundle, CodexGeneratedSkill } from "../types/codex"
|
||||
import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
|
||||
import {
|
||||
@@ -16,17 +16,18 @@ export function convertClaudeToCodex(
|
||||
plugin: ClaudePlugin,
|
||||
_options: ClaudeToCodexOptions,
|
||||
): CodexBundle {
|
||||
const platformSkills = filterSkillsByPlatform(plugin.skills, "codex")
|
||||
const invocableCommands = plugin.commands.filter((command) => !command.disableModelInvocation)
|
||||
const applyCompoundWorkflowModel = shouldApplyCompoundWorkflowModel(plugin)
|
||||
const canonicalWorkflowSkills = applyCompoundWorkflowModel
|
||||
? plugin.skills.filter((skill) => isCanonicalCodexWorkflowSkill(skill.name))
|
||||
? platformSkills.filter((skill) => isCanonicalCodexWorkflowSkill(skill.name))
|
||||
: []
|
||||
const deprecatedWorkflowAliases = applyCompoundWorkflowModel
|
||||
? plugin.skills.filter((skill) => isDeprecatedCodexWorkflowAlias(skill.name))
|
||||
? platformSkills.filter((skill) => isDeprecatedCodexWorkflowAlias(skill.name))
|
||||
: []
|
||||
const copiedSkills = applyCompoundWorkflowModel
|
||||
? plugin.skills.filter((skill) => !isDeprecatedCodexWorkflowAlias(skill.name))
|
||||
: plugin.skills
|
||||
? platformSkills.filter((skill) => !isDeprecatedCodexWorkflowAlias(skill.name))
|
||||
: platformSkills
|
||||
const skillDirs = copiedSkills.map((skill) => ({
|
||||
name: skill.name,
|
||||
sourceDir: skill.sourceDir,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import { sanitizePathName } from "../utils/files"
|
||||
import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
|
||||
import { type ClaudeAgent, type ClaudeCommand, type ClaudeMcpServer, type ClaudePlugin, filterSkillsByPlatform } from "../types/claude"
|
||||
import type {
|
||||
CopilotAgent,
|
||||
CopilotBundle,
|
||||
@@ -23,7 +23,7 @@ export function convertClaudeToCopilot(
|
||||
const agents = plugin.agents.map((agent) => convertAgent(agent, usedAgentNames))
|
||||
|
||||
// Reserve sanitized skill names so generated skills (from commands) don't collide on disk
|
||||
const skillDirs = plugin.skills.map((skill) => {
|
||||
const skillDirs = filterSkillsByPlatform(plugin.skills, "copilot").map((skill) => {
|
||||
usedSkillNames.add(sanitizePathName(skill.name))
|
||||
return {
|
||||
name: skill.name,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import type { ClaudeAgent, ClaudeCommand, ClaudePlugin } from "../types/claude"
|
||||
import { type ClaudeAgent, type ClaudeCommand, type ClaudePlugin, filterSkillsByPlatform } from "../types/claude"
|
||||
import type { DroidBundle, DroidCommandFile, DroidAgentFile } from "../types/droid"
|
||||
import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
|
||||
|
||||
@@ -45,7 +45,7 @@ export function convertClaudeToDroid(
|
||||
): DroidBundle {
|
||||
const commands = plugin.commands.map((command) => convertCommand(command))
|
||||
const droids = plugin.agents.map((agent) => convertAgent(agent))
|
||||
const skillDirs = plugin.skills.map((skill) => ({
|
||||
const skillDirs = filterSkillsByPlatform(plugin.skills, "droid").map((skill) => ({
|
||||
name: skill.name,
|
||||
sourceDir: skill.sourceDir,
|
||||
}))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
|
||||
import { type ClaudeAgent, type ClaudeCommand, type ClaudeMcpServer, type ClaudePlugin, filterSkillsByPlatform } from "../types/claude"
|
||||
import type { GeminiBundle, GeminiCommand, GeminiMcpServer, GeminiSkill } from "../types/gemini"
|
||||
import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
|
||||
|
||||
@@ -14,7 +14,8 @@ export function convertClaudeToGemini(
|
||||
const usedSkillNames = new Set<string>()
|
||||
const usedCommandNames = new Set<string>()
|
||||
|
||||
const skillDirs = plugin.skills.map((skill) => ({
|
||||
const platformSkills = filterSkillsByPlatform(plugin.skills, "gemini")
|
||||
const skillDirs = platformSkills.map((skill) => ({
|
||||
name: skill.name,
|
||||
sourceDir: skill.sourceDir,
|
||||
}))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFileSync, existsSync } from "fs"
|
||||
import path from "path"
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
|
||||
import { type ClaudeAgent, type ClaudeCommand, type ClaudeMcpServer, type ClaudePlugin, filterSkillsByPlatform } from "../types/claude"
|
||||
import type {
|
||||
KiroAgent,
|
||||
KiroAgentConfig,
|
||||
@@ -36,7 +36,7 @@ export function convertClaudeToKiro(
|
||||
const usedSkillNames = new Set<string>()
|
||||
|
||||
// Pass-through skills are processed first — they're the source of truth
|
||||
const skillDirs = plugin.skills.map((skill) => ({
|
||||
const skillDirs = filterSkillsByPlatform(plugin.skills, "kiro").map((skill) => ({
|
||||
name: skill.name,
|
||||
sourceDir: skill.sourceDir,
|
||||
}))
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import { normalizeModelWithProvider } from "../utils/model"
|
||||
import { sanitizePathName } from "../utils/files"
|
||||
import type {
|
||||
ClaudeAgent,
|
||||
ClaudeCommand,
|
||||
ClaudePlugin,
|
||||
ClaudeMcpServer,
|
||||
import {
|
||||
type ClaudeAgent,
|
||||
type ClaudeCommand,
|
||||
type ClaudePlugin,
|
||||
type ClaudeMcpServer,
|
||||
filterSkillsByPlatform,
|
||||
} from "../types/claude"
|
||||
import type {
|
||||
OpenClawBundle,
|
||||
@@ -29,7 +30,8 @@ export function convertClaudeToOpenClaw(
|
||||
|
||||
const skills: OpenClawSkillFile[] = [...agentSkills, ...commandSkills]
|
||||
|
||||
const skillDirCopies = plugin.skills.map((skill) => ({
|
||||
const platformSkills = filterSkillsByPlatform(plugin.skills, "openclaw")
|
||||
const skillDirCopies = platformSkills.map((skill) => ({
|
||||
sourceDir: skill.sourceDir,
|
||||
name: skill.name,
|
||||
}))
|
||||
@@ -37,7 +39,7 @@ export function convertClaudeToOpenClaw(
|
||||
const allSkillDirs = [
|
||||
...agentSkills.map((s) => sanitizePathName(s.dir)),
|
||||
...commandSkills.map((s) => sanitizePathName(s.dir)),
|
||||
...plugin.skills.map((s) => sanitizePathName(s.name)),
|
||||
...platformSkills.map((s) => sanitizePathName(s.name)),
|
||||
]
|
||||
|
||||
const manifest = buildManifest(plugin, allSkillDirs)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import { normalizeModelWithProvider } from "../utils/model"
|
||||
import type {
|
||||
ClaudeAgent,
|
||||
ClaudeCommand,
|
||||
ClaudeHooks,
|
||||
ClaudePlugin,
|
||||
ClaudeMcpServer,
|
||||
import {
|
||||
type ClaudeAgent,
|
||||
type ClaudeCommand,
|
||||
type ClaudeHooks,
|
||||
type ClaudePlugin,
|
||||
type ClaudeMcpServer,
|
||||
filterSkillsByPlatform,
|
||||
} from "../types/claude"
|
||||
import type {
|
||||
OpenCodeBundle,
|
||||
@@ -83,7 +84,7 @@ export function convertClaudeToOpenCode(
|
||||
agents: agentFiles,
|
||||
commandFiles: cmdFiles,
|
||||
plugins,
|
||||
skillDirs: plugin.skills.map((skill) => ({ sourceDir: skill.sourceDir, name: skill.name })),
|
||||
skillDirs: filterSkillsByPlatform(plugin.skills, "opencode").map((skill) => ({ sourceDir: skill.sourceDir, name: skill.name })),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
|
||||
import { type ClaudeAgent, type ClaudeCommand, type ClaudeMcpServer, type ClaudePlugin, filterSkillsByPlatform } from "../types/claude"
|
||||
import type {
|
||||
PiBundle,
|
||||
PiGeneratedSkill,
|
||||
@@ -17,8 +17,9 @@ export function convertClaudeToPi(
|
||||
plugin: ClaudePlugin,
|
||||
_options: ClaudeToPiOptions,
|
||||
): PiBundle {
|
||||
const platformSkills = filterSkillsByPlatform(plugin.skills, "pi")
|
||||
const promptNames = new Set<string>()
|
||||
const usedSkillNames = new Set<string>(plugin.skills.map((skill) => normalizeName(skill.name)))
|
||||
const usedSkillNames = new Set<string>(platformSkills.map((skill) => normalizeName(skill.name)))
|
||||
|
||||
const prompts = plugin.commands
|
||||
.filter((command) => !command.disableModelInvocation)
|
||||
@@ -35,7 +36,7 @@ export function convertClaudeToPi(
|
||||
|
||||
return {
|
||||
prompts,
|
||||
skillDirs: plugin.skills.map((skill) => ({
|
||||
skillDirs: platformSkills.map((skill) => ({
|
||||
name: skill.name,
|
||||
sourceDir: skill.sourceDir,
|
||||
})),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import { normalizeModelWithProvider } from "../utils/model"
|
||||
import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
|
||||
import { type ClaudeAgent, type ClaudeCommand, type ClaudeMcpServer, type ClaudePlugin, filterSkillsByPlatform } from "../types/claude"
|
||||
import type {
|
||||
QwenAgentFile,
|
||||
QwenBundle,
|
||||
@@ -16,6 +16,7 @@ export type ClaudeToQwenOptions = {
|
||||
}
|
||||
|
||||
export function convertClaudeToQwen(plugin: ClaudePlugin, options: ClaudeToQwenOptions): QwenBundle {
|
||||
const platformSkills = filterSkillsByPlatform(plugin.skills, "qwen")
|
||||
const agentFiles = plugin.agents.map((agent) => convertAgent(agent, options))
|
||||
const cmdFiles = convertCommands(plugin.commands)
|
||||
const mcp = plugin.mcpServers ? convertMcp(plugin.mcpServers) : undefined
|
||||
@@ -43,7 +44,7 @@ export function convertClaudeToQwen(plugin: ClaudePlugin, options: ClaudeToQwenO
|
||||
config,
|
||||
agents: agentFiles,
|
||||
commandFiles: cmdFiles,
|
||||
skillDirs: plugin.skills.map((skill) => ({ sourceDir: skill.sourceDir, name: skill.name })),
|
||||
skillDirs: platformSkills.map((skill) => ({ sourceDir: skill.sourceDir, name: skill.name })),
|
||||
contextFile,
|
||||
}
|
||||
}
|
||||
@@ -181,10 +182,11 @@ function generateContextFile(plugin: ClaudePlugin): string {
|
||||
}
|
||||
|
||||
// Skills section
|
||||
if (plugin.skills.length > 0) {
|
||||
const qwenSkills = filterSkillsByPlatform(plugin.skills, "qwen")
|
||||
if (qwenSkills.length > 0) {
|
||||
sections.push("## Skills")
|
||||
sections.push("")
|
||||
for (const skill of plugin.skills) {
|
||||
for (const skill of qwenSkills) {
|
||||
sections.push(`- ${skill.name}`)
|
||||
}
|
||||
sections.push("")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { formatFrontmatter } from "../utils/frontmatter"
|
||||
import { sanitizePathName } from "../utils/files"
|
||||
import { findServersWithPotentialSecrets } from "../utils/secrets"
|
||||
import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
|
||||
import { type ClaudeAgent, type ClaudeCommand, type ClaudeMcpServer, type ClaudePlugin, filterSkillsByPlatform } from "../types/claude"
|
||||
import type { WindsurfBundle, WindsurfGeneratedSkill, WindsurfMcpConfig, WindsurfMcpServerEntry, WindsurfWorkflow } from "../types/windsurf"
|
||||
import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
|
||||
|
||||
@@ -16,7 +16,7 @@ export function convertClaudeToWindsurf(
|
||||
const knownAgentNames = plugin.agents.map((a) => normalizeName(a.name))
|
||||
|
||||
// Pass-through skills (collected first so agent skill names can deduplicate against them)
|
||||
const skillDirs = plugin.skills.map((skill) => ({
|
||||
const skillDirs = filterSkillsByPlatform(plugin.skills, "windsurf").map((skill) => ({
|
||||
name: skill.name,
|
||||
sourceDir: skill.sourceDir,
|
||||
}))
|
||||
|
||||
@@ -107,11 +107,13 @@ async function loadSkills(skillsDirs: string[]): Promise<ClaudeSkill[]> {
|
||||
const { data } = parseFrontmatter(raw, file)
|
||||
const name = (data.name as string) ?? path.basename(path.dirname(file))
|
||||
const disableModelInvocation = data["disable-model-invocation"] === true ? true : undefined
|
||||
const ce_platforms = Array.isArray(data.ce_platforms) ? (data.ce_platforms as string[]) : undefined
|
||||
skills.push({
|
||||
name,
|
||||
description: data.description as string | undefined,
|
||||
argumentHint: data["argument-hint"] as string | undefined,
|
||||
disableModelInvocation,
|
||||
ce_platforms,
|
||||
sourceDir: path.dirname(file),
|
||||
skillPath: file,
|
||||
})
|
||||
|
||||
@@ -49,10 +49,19 @@ export type ClaudeSkill = {
|
||||
description?: string
|
||||
argumentHint?: string
|
||||
disableModelInvocation?: boolean
|
||||
ce_platforms?: string[]
|
||||
sourceDir: string
|
||||
skillPath: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter skills to those available on a given platform.
|
||||
* Skills without a `platforms` field are available everywhere.
|
||||
*/
|
||||
export function filterSkillsByPlatform(skills: ClaudeSkill[], platform: string): ClaudeSkill[] {
|
||||
return skills.filter((skill) => !skill.ce_platforms || skill.ce_platforms.includes(platform))
|
||||
}
|
||||
|
||||
export type ClaudePlugin = {
|
||||
root: string
|
||||
manifest: ClaudeManifest
|
||||
|
||||
Reference in New Issue
Block a user