From 47453c149fd6bd53f3685026f62f1380933df4e6 Mon Sep 17 00:00:00 2001 From: Alex Holmberg Date: Tue, 31 Mar 2026 13:32:33 +0200 Subject: [PATCH 1/2] feat: updating installer due to bad marketn installation --- installer/package.json | 2 +- .../.claude-plugin/plugin.json | 2 +- installer/scripts/copy-skills.js | 52 +++++++++++++++++-- installer/src/transformers/claude.ts | 50 ++++++++++-------- installer/src/transformers/codex.ts | 4 +- installer/src/transformers/cursor.ts | 3 +- installer/src/transformers/windsurf.ts | 3 +- installer/tests/transformers/codex.test.ts | 6 +-- 8 files changed, 89 insertions(+), 33 deletions(-) diff --git a/installer/package.json b/installer/package.json index c764709a..fb46d292 100644 --- a/installer/package.json +++ b/installer/package.json @@ -1,6 +1,6 @@ { "name": "syncable-cli-skills", - "version": "0.1.5", + "version": "0.1.8", "type": "module", "description": "Install Syncable CLI skills for AI coding agents (Claude Code, Cursor, Windsurf, Codex, Gemini CLI)", "license": "GPL-3.0", diff --git a/installer/plugins/syncable-cli-skills/.claude-plugin/plugin.json b/installer/plugins/syncable-cli-skills/.claude-plugin/plugin.json index 4e3d689f..fd74b057 100644 --- a/installer/plugins/syncable-cli-skills/.claude-plugin/plugin.json +++ b/installer/plugins/syncable-cli-skills/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "syncable-cli-skills", "description": "Syncable CLI skills for project analysis, security scanning, vulnerability detection, dependency auditing, IaC validation, Kubernetes optimization, and cloud deployment.", - "version": "0.1.0", + "version": "0.1.8", "author": { "name": "Syncable", "email": "support@syncable.dev" diff --git a/installer/scripts/copy-skills.js b/installer/scripts/copy-skills.js index 9462781a..06e7ed90 100644 --- a/installer/scripts/copy-skills.js +++ b/installer/scripts/copy-skills.js @@ -1,6 +1,6 @@ import pkg from 'fs-extra'; -const { copySync, removeSync, existsSync } = pkg; -import { resolve, dirname } from 'path'; +const { copySync, removeSync, existsSync, mkdirpSync, writeFileSync, readdirSync, readFileSync } = pkg; +import { resolve, dirname, basename } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -12,7 +12,53 @@ if (!existsSync(source)) { process.exit(1); } +// Copy raw skills to installer/skills/ (used by the npm package at runtime) removeSync(dest); copySync(source, dest); - console.log(`Copied skills from ${source} to ${dest}`); + +// Also regenerate installer/plugins/syncable-cli-skills/skills/ +// so the Claude Code marketplace plugin stays in sync with the source skills. +const pluginSkillsDir = resolve(__dirname, '..', 'plugins', 'syncable-cli-skills', 'skills'); +removeSync(pluginSkillsDir); + +function transformSkillFile(filePath) { + const raw = readFileSync(filePath, 'utf-8'); + // Parse YAML frontmatter (---\n...\n---\n) + const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); + if (!match) return null; + + const frontmatterRaw = match[1]; + const body = match[2]; + + // Extract description value (handles multi-line descriptions with quotes) + const descMatch = frontmatterRaw.match(/^description:\s*(.+)$/m); + if (!descMatch) return null; + + const desc = descMatch[1].trim().replace(/^["']|["']$/g, ''); + const safeDesc = desc.replace(/"/g, '\\"'); + + return `---\ndescription: "${safeDesc}"\n---\n${body}`; +} + +let skillCount = 0; +for (const category of ['commands', 'workflows']) { + const categoryDir = resolve(source, category); + if (!existsSync(categoryDir)) continue; + + for (const file of readdirSync(categoryDir)) { + if (!file.endsWith('.md')) continue; + const skillName = basename(file, '.md'); + const content = transformSkillFile(resolve(categoryDir, file)); + if (!content) { + console.warn(`Warning: could not parse frontmatter for ${file}, skipping`); + continue; + } + const outDir = resolve(pluginSkillsDir, skillName); + mkdirpSync(outDir); + writeFileSync(resolve(outDir, 'SKILL.md'), content); + skillCount++; + } +} + +console.log(`Generated ${skillCount} plugin skills at ${pluginSkillsDir}`); diff --git a/installer/src/transformers/claude.ts b/installer/src/transformers/claude.ts index 11f57217..2593a17f 100644 --- a/installer/src/transformers/claude.ts +++ b/installer/src/transformers/claude.ts @@ -6,7 +6,7 @@ import { TransformResult } from './types.js'; import { execCommand, commandExists } from '../utils.js'; const PLUGIN_NAME = 'syncable-cli-skills'; -const PLUGIN_VERSION = '0.1.0'; +const PLUGIN_VERSION = '0.1.8'; const MARKETPLACE_NAME = 'syncable'; const MARKETPLACE_REPO = 'syncable-dev/syncable-cli'; @@ -19,8 +19,6 @@ export function transformForClaude(skill: Skill): TransformResult[] { const safeDesc = skill.frontmatter.description .replace(/"/g, '\\"') - .replace(/: /g, ' - ') - .replace(/Trigger on:.*$/, '') .trim(); const content = `---\ndescription: "${safeDesc}"\n---\n\n${skill.body}`; @@ -154,14 +152,15 @@ function enablePluginInSettings(): void { settings.extraKnownMarketplaces = {}; } const marketplaces = settings.extraKnownMarketplaces as Record; - if (!marketplaces[MARKETPLACE_NAME]) { - marketplaces[MARKETPLACE_NAME] = { - source: { - source: 'github', - repo: MARKETPLACE_REPO, - }, - }; - } + // Always overwrite the marketplace entry to ensure it is canonical and free + // of non-standard fields (e.g. a stale "path" override added by Claude Code + // dev-mode that causes the plugin to be loaded from the local filesystem). + marketplaces[MARKETPLACE_NAME] = { + source: { + source: 'github', + repo: MARKETPLACE_REPO, + }, + }; fs.mkdirSync(path.dirname(settingsFile), { recursive: true }); fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2)); @@ -174,22 +173,32 @@ function enablePluginInSettings(): void { * 2. Fall back to manual: write cache files + update settings.json */ export async function installClaudePlugin(skills: Skill[]): Promise<{ cacheDir: string; skillCount: number }> { - // ── Attempt 1: Official CLI ──────────────────────────────────────── - const cliSuccess = await tryClaudeCliInstall(); - if (cliSuccess) { - return { cacheDir: getClaudePluginCacheDir(), skillCount: skills.length }; - } + // Try the official CLI first — this handles enabledPlugins registration. + // We don't return early on success because the CLI may have cached an old + // version of the plugin that is missing the skills directory (e.g. from a + // previous install before skills were added, or from a stale npx cache). + // We always write skills directly to the cache so they're guaranteed to exist. + await tryClaudeCliInstall(); - // ── Attempt 2: Manual write + settings.json ──────────────────────── const cacheDir = getClaudePluginCacheDir(); - // Clear old skills + // Remove stale older-version cache entries so Claude Code doesn't load an + // empty/outdated version instead of the current one. + const pluginRootDir = path.dirname(cacheDir); + if (fs.existsSync(pluginRootDir)) { + for (const entry of fs.readdirSync(pluginRootDir)) { + if (entry !== PLUGIN_VERSION) { + fs.rmSync(path.join(pluginRootDir, entry), { recursive: true, force: true }); + } + } + } + + // Clear old skills and rewrite them so the cache is always up to date. const skillsDir = path.join(cacheDir, 'skills'); if (fs.existsSync(skillsDir)) { fs.rmSync(skillsDir, { recursive: true }); } - // Write each skill for (const skill of skills) { const results = transformForClaude(skill); for (const { relativePath, content } of results) { @@ -199,10 +208,7 @@ export async function installClaudePlugin(skills: Skill[]): Promise<{ cacheDir: } } - // Write plugin manifest writePluginManifest(cacheDir); - - // Enable in settings.json (THE KEY FIX) enablePluginInSettings(); return { cacheDir, skillCount: skills.length }; diff --git a/installer/src/transformers/codex.ts b/installer/src/transformers/codex.ts index 1330796c..71b50ab7 100644 --- a/installer/src/transformers/codex.ts +++ b/installer/src/transformers/codex.ts @@ -2,6 +2,8 @@ import { Skill } from '../skills.js'; import { TransformResult } from './types.js'; export function transformForCodex(skill: Skill): TransformResult[] { - const content = `---\nname: ${skill.frontmatter.name}\ndescription: ${skill.frontmatter.description}\n---\n\n${skill.body}`; + const safeName = skill.frontmatter.name.replace(/"/g, '\\"'); + const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"'); + const content = `---\nname: "${safeName}"\ndescription: "${safeDesc}"\n---\n\n${skill.body}`; return [{ relativePath: `${skill.frontmatter.name}/SKILL.md`, content }]; } diff --git a/installer/src/transformers/cursor.ts b/installer/src/transformers/cursor.ts index 3c0c862d..9348c75b 100644 --- a/installer/src/transformers/cursor.ts +++ b/installer/src/transformers/cursor.ts @@ -3,6 +3,7 @@ import { TransformResult } from './types.js'; export function transformForCursor(skill: Skill): TransformResult[] { const filename = skill.frontmatter.name + '.mdc'; - const content = `---\ndescription: "Syncable CLI: ${skill.frontmatter.description}"\nglobs:\nalwaysApply: true\n---\n\n${skill.body}`; + const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"'); + const content = `---\ndescription: "Syncable CLI: ${safeDesc}"\nglobs:\nalwaysApply: true\n---\n\n${skill.body}`; return [{ relativePath: filename, content }]; } diff --git a/installer/src/transformers/windsurf.ts b/installer/src/transformers/windsurf.ts index 1c0742cc..18277b00 100644 --- a/installer/src/transformers/windsurf.ts +++ b/installer/src/transformers/windsurf.ts @@ -3,6 +3,7 @@ import { TransformResult } from './types.js'; export function transformForWindsurf(skill: Skill): TransformResult[] { const filename = skill.frontmatter.name + '.md'; - const content = `---\ntrigger: always\ndescription: "Syncable CLI: ${skill.frontmatter.description}"\n---\n\n${skill.body}`; + const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"'); + const content = `---\ntrigger: always\ndescription: "Syncable CLI: ${safeDesc}"\n---\n\n${skill.body}`; return [{ relativePath: filename, content }]; } diff --git a/installer/tests/transformers/codex.test.ts b/installer/tests/transformers/codex.test.ts index c5d95142..d950985b 100644 --- a/installer/tests/transformers/codex.test.ts +++ b/installer/tests/transformers/codex.test.ts @@ -15,10 +15,10 @@ describe('transformForCodex', () => { expect(result[0].relativePath).toBe('syncable-analyze/SKILL.md'); }); - it('preserves frontmatter in SKILL.md', () => { + it('preserves frontmatter in SKILL.md with quoted values', () => { const result = transformForCodex(sampleSkill); - expect(result[0].content).toContain('name: syncable-analyze'); - expect(result[0].content).toContain('description: Use when analyzing a project'); + expect(result[0].content).toContain('name: "syncable-analyze"'); + expect(result[0].content).toContain('description: "Use when analyzing a project"'); }); it('preserves body content', () => { From 22e26fe724e57d47e75211eda68e4df1127b940d Mon Sep 17 00:00:00 2001 From: Alex Holmberg Date: Tue, 31 Mar 2026 13:33:56 +0200 Subject: [PATCH 2/2] feat: updated marketplace --- .claude-plugin/marketplace.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 32f8ccec..1a9330eb 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,14 +6,14 @@ }, "metadata": { "description": "Syncable CLI skills for AI coding agents — project analysis, security, vulnerabilities, dependencies, IaC validation, and cloud deployment.", - "version": "0.1.0" + "version": "0.1.8" }, "plugins": [ { "name": "syncable-cli-skills", "source": "./installer/plugins/syncable-cli-skills", "description": "Syncable CLI skills for project analysis, security scanning, vulnerability detection, dependency auditing, IaC validation, Kubernetes optimization, and cloud deployment.", - "version": "0.1.0", + "version": "0.1.8", "author": { "name": "Syncable", "email": "support@syncable.dev"