From f029a4178873bd9f877213f72d649a6362cadac9 Mon Sep 17 00:00:00 2001 From: danniel <1037897418@qq.com> Date: Mon, 6 Apr 2026 01:22:17 +0800 Subject: [PATCH 1/3] feat: enable Cursor marketplace manifest and plugin manifest generation Co-Authored-By: Claude Opus 4.6 (1M context) --- src/index.ts | 11 ++++------- src/writer/cursor.ts | 9 ++++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index bdd9088..1d43e55 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,9 +13,7 @@ import { writeFile } from './utils/fs.js'; import { parseGitHubSource, downloadGitHubRepo, cleanupTempDir, getTempRoot } from './github.js'; import { selectPlugins, selectPlatforms, runWizard, log } from './tui.js'; import type { Platform, ConvertResult, ScanResult, PluginScanResult, MarketplaceScanResult } from './types.js'; -import { convertMarketplaceForCodex } from './converter/pluginManifest.js'; -// TODO: re-enable after Cursor marketplace test validation -// import { convertMarketplaceForCursor } from './converter/pluginManifest.js'; +import { convertMarketplaceForCodex, convertMarketplaceForCursor } from './converter/pluginManifest.js'; const program = new Command(); @@ -296,10 +294,9 @@ function generateMarketplaceManifest( case 'codex': files.push(convertMarketplaceForCodex(marketplace, plugins)); break; - // TODO: Cursor marketplace generation — pending test validation - // case 'cursor': - // files.push(convertMarketplaceForCursor(marketplace, plugins)); - // break; + case 'cursor': + files.push(convertMarketplaceForCursor(marketplace, plugins)); + break; // OpenCode and Antigravity don't support plugin manifest/marketplace. // Their resource conversion (skills, agents, etc.) is handled normally by the writers. } diff --git a/src/writer/cursor.ts b/src/writer/cursor.ts index 5964b4d..d050800 100644 --- a/src/writer/cursor.ts +++ b/src/writer/cursor.ts @@ -5,8 +5,7 @@ import { convertMCP } from '../converter/mcp.js'; import { convertAgent } from '../converter/agent.js'; import { convertCommand } from '../converter/command.js'; import { convertHooks } from '../converter/hooks.js'; -// TODO: re-enable after Cursor plugin manifest test validation -// import { convertPluginManifestForCursor } from '../converter/pluginManifest.js'; +import { convertPluginManifestForCursor } from '../converter/pluginManifest.js'; export function generateCursor(scan: ScanResult): ConvertResult { const files: ConvertedFile[] = []; @@ -48,9 +47,9 @@ export function generateCursor(scan: ScanResult): ConvertResult { files.push({ path: pf.relativePath, content: pf.content, type: 'resource' }); } - // TODO: Cursor plugin manifest generation — pending test validation - // const meta = (scan as PluginScanResult).meta; - // files.push(convertPluginManifestForCursor(scan, meta)); + // Cursor plugin manifest generation + const meta = (scan as PluginScanResult).meta; + files.push(convertPluginManifestForCursor(scan, meta)); // Remap paths: .cursor/xxx → plugin format (skills/, agents/, etc.) for (const file of files) { From c264ec81c361a2e6c73aca6530430bdde5b6bb17 Mon Sep 17 00:00:00 2001 From: danniel <1037897418@qq.com> Date: Mon, 6 Apr 2026 02:49:10 +0800 Subject: [PATCH 2/3] refactor: move Cursor .cursor/ prefix from converters to writer for cleaner separation Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/settings.local.json | 3 +- README.md | 19 ++-- README.zh-CN.md | 41 +++---- src/__tests__/agent.test.ts | 2 +- src/__tests__/command.test.ts | 2 +- src/__tests__/cursor-writer.test.ts | 105 ++++++++++++++++++ src/__tests__/instructions.test.ts | 4 +- src/__tests__/mcp.test.ts | 2 +- src/__tests__/pluginManifest.test.ts | 2 +- src/__tests__/skill.test.ts | 2 +- src/__tests__/superpowers-integration.test.ts | 27 +++-- src/converter/agent.ts | 2 +- src/converter/command.ts | 2 +- src/converter/hooks.ts | 2 +- src/converter/instructions.ts | 4 +- src/converter/mcp.ts | 2 +- src/converter/pluginManifest.ts | 12 +- src/converter/skill.ts | 2 +- src/writer/cursor.ts | 22 ++-- 19 files changed, 183 insertions(+), 74 deletions(-) create mode 100644 src/__tests__/cursor-writer.test.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a2cf260..a8f6df9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,7 +11,8 @@ "WebFetch(domain:github.com)", "Bash(npx tsc:*)", "Bash(npx vitest:*)", - "Bash(git clone:*)" + "Bash(git clone:*)", + "mcp__ide__getDiagnostics" ] } } diff --git a/README.md b/README.md index e658cec..785fa01 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,17 @@ acplugin scan anthropics/claude-code ## Supported Conversions -| Resource | Codex CLI | OpenCode | Cursor | Antigravity | -| ---------------- | ------------------------- | ------------------------- | --------------------- | ----------------------- | -| **Skills** | `.agents/skills/` | `.opencode/skills/` | `.cursor/skills/` | `.agent/skills/` | -| **Instructions** | `AGENTS.md` | `AGENTS.md` | `.cursor/rules/*.mdc` | `GEMINI.md` | -| **MCP Servers** | `.codex/config.toml` | `opencode.json` | `.cursor/mcp.json` | `.gemini/settings.json` | -| **Agents** | `.codex/agents/*.toml` | `.opencode/agents/*.md` | `.cursor/agents/*.md` | `.gemini/agents/*.md` | -| **Commands** | Converted to Skills | `.opencode/commands/` | `.cursor/commands/` | Converted to Skills | -| **Hooks** | Documented in `AGENTS.md` | Documented in `AGENTS.md` | Warnings only | Warnings only | +| Resource | Codex CLI | OpenCode | Cursor | Antigravity | +| -------------------- | --------------------------- | ------------------------- | ------------------------------ | ----------------------- | +| **Skills** | `.agents/skills/` | `.opencode/skills/` | `.cursor/skills/` | `.agent/skills/` | +| **Instructions** | `AGENTS.md` | `AGENTS.md` | `.cursor/rules/*.mdc` | `GEMINI.md` | +| **MCP Servers** | `.codex/config.toml` | `opencode.json` | `.cursor/mcp.json` | `.gemini/settings.json` | +| **Agents** | `.codex/agents/*.toml` | `.opencode/agents/*.md` | `.cursor/agents/*.md` | `.gemini/agents/*.md` | +| **Commands** | Converted to Skills | `.opencode/commands/` | `.cursor/commands/` | Converted to Skills | +| **Hooks** | Documented in `AGENTS.md` | Documented in `AGENTS.md` | `.cursor/hooks.json` | Warnings only | +| **Plugin Manifest** | `.codex-plugin/plugin.json` | — | `.cursor-plugin/plugin.json` | — | +| **Marketplace** | `.agents/plugins/marketplace.json` | — | `.cursor-plugin/marketplace.json` | — | +| **Scripts** | `scripts/` (preserved) | `scripts/` (preserved) | `.cursor/scripts/` (preserved) | `scripts/` (preserved) | ### Model Mapping diff --git a/README.zh-CN.md b/README.zh-CN.md index ec1a249..0b25d3f 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -43,22 +43,25 @@ acplugin scan anthropics/claude-code ## 支持的转换 -| 资源类型 | Codex CLI | OpenCode | Cursor | Antigravity | -|---------|-----------|----------|--------|-------------| -| **Skills** | `.agents/skills/` | `.opencode/skills/` | `.cursor/skills/` | `.agent/skills/` | -| **指令** | `AGENTS.md` | `AGENTS.md` | `.cursor/rules/*.mdc` | `GEMINI.md` | -| **MCP 服务器** | `.codex/config.toml` | `opencode.json` | `.cursor/mcp.json` | `.gemini/settings.json` | -| **Agents** | `.codex/agents/*.toml` | `.opencode/agents/*.md` | `.cursor/agents/*.md` | `.gemini/agents/*.md` | -| **Commands** | 转换为 Skills | `.opencode/commands/` | `.cursor/commands/` | 转换为 Skills | -| **Hooks** | 记录在 `AGENTS.md` | 记录在 `AGENTS.md` | 仅输出警告 | 仅输出警告 | +| 资源类型 | Codex CLI | OpenCode | Cursor | Antigravity | +| -------------------- | ---------------------------------- | ----------------------- | --------------------------------- | ----------------------- | +| **Skills** | `.agents/skills/` | `.opencode/skills/` | `.cursor/skills/` | `.agent/skills/` | +| **指令** | `AGENTS.md` | `AGENTS.md` | `.cursor/rules/*.mdc` | `GEMINI.md` | +| **MCP 服务器** | `.codex/config.toml` | `opencode.json` | `.cursor/mcp.json` | `.gemini/settings.json` | +| **Agents** | `.codex/agents/*.toml` | `.opencode/agents/*.md` | `.cursor/agents/*.md` | `.gemini/agents/*.md` | +| **Commands** | 转换为 Skills | `.opencode/commands/` | `.cursor/commands/` | 转换为 Skills | +| **Hooks** | 记录在 `AGENTS.md` | 记录在 `AGENTS.md` | `.cursor/hooks.json` | 仅输出警告 | +| **plugin.json** | `.codex-plugin/plugin.json` | — | `.cursor-plugin/plugin.json` | — | +| **marketplace.json** | `.agents/plugins/marketplace.json` | — | `.cursor-plugin/marketplace.json` | — | +| **Scripts** | `scripts/`(保持原路径) | `scripts/`(保持原路径) | `.cursor/scripts/`(保持原路径) | `scripts/`(保持原路径)| ### 模型映射 -| Claude Code | → Codex | → Antigravity | -|-------------|---------|---------------| -| `sonnet` / `opus` | `gpt-5.4` | `gemini-3-pro` | -| `haiku` | `gpt-5.4` | `gemini-3-flash` | -| (未指定) | `gpt-5.4` | `gemini-3-pro` | +| Claude Code | → Codex | → Antigravity | +| ----------------- | --------- | ---------------- | +| `sonnet` / `opus` | `gpt-5.4` | `gemini-3-pro` | +| `haiku` | `gpt-5.4` | `gemini-3-flash` | +| (未指定) | `gpt-5.4` | `gemini-3-pro` | OpenCode 和 Cursor 保持原始模型值不映射。 @@ -92,13 +95,13 @@ acplugin convert . --dry-run # 预览模式,不写入文件 **选项:** -| 选项 | 说明 | -|------|------| +| 选项 | 说明 | +| ---------------------- | ------------------------------------------------------------------ | | `-t, --to ` | 目标平台(逗号分隔:`codex`、`opencode`、`cursor`、`antigravity`) | -| `-o, --output ` | 输出目录 | -| `-a, --all` | 全部转换,跳过交互选择 | -| `-p, --path ` | 仓库内子路径 | -| `--dry-run` | 预览生成的文件,不实际写入 | +| `-o, --output ` | 输出目录 | +| `-a, --all` | 全部转换,跳过交互选择 | +| `-p, --path ` | 仓库内子路径 | +| `--dry-run` | 预览生成的文件,不实际写入 | ## 使用示例 diff --git a/src/__tests__/agent.test.ts b/src/__tests__/agent.test.ts index a8d6b86..7e5b428 100644 --- a/src/__tests__/agent.test.ts +++ b/src/__tests__/agent.test.ts @@ -73,7 +73,7 @@ describe('convertAgent', () => { it('converts to cursor as agent file', () => { const result = convertAgent(sampleAgent, 'cursor'); - expect(result.path).toBe('.cursor/agents/code-reviewer.md'); + expect(result.path).toBe('agents/code-reviewer.md'); expect(result.content).toContain('name: code-reviewer'); expect(result.content).toContain('description: Reviews code for bugs'); }); diff --git a/src/__tests__/command.test.ts b/src/__tests__/command.test.ts index 84b055f..ce252c9 100644 --- a/src/__tests__/command.test.ts +++ b/src/__tests__/command.test.ts @@ -24,7 +24,7 @@ describe('convertCommand', () => { it('converts to cursor as command file', () => { const result = convertCommand(sampleCommand, 'cursor'); - expect(result.path).toBe('.cursor/commands/deploy.md'); + expect(result.path).toBe('commands/deploy.md'); expect(result.content).toContain('Deploy to $1'); }); }); diff --git a/src/__tests__/cursor-writer.test.ts b/src/__tests__/cursor-writer.test.ts new file mode 100644 index 0000000..954128b --- /dev/null +++ b/src/__tests__/cursor-writer.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect } from 'vitest'; +import { generateCursor } from '../writer/cursor.js'; +import type { ScanResult } from '../types.js'; + +function makeScan(overrides: Partial = {}): ScanResult { + return { + skills: [], + instructions: [], + agents: [], + commands: [], + mcp: null, + hooks: null, + pluginFiles: [], + ...overrides, + } as ScanResult; +} + +describe('generateCursor — addCursorPrefix', () => { + it('adds .cursor/ prefix to skill paths', () => { + const result = generateCursor(makeScan({ + skills: [{ + dirName: 'my-skill', + frontmatter: { name: 'my-skill' }, + body: 'Do stuff', + sourcePath: '/fake/skills/my-skill/SKILL.md', + auxFiles: [], + }], + })); + const skill = result.files.find(f => f.type === 'skill'); + expect(skill?.path).toBe('.cursor/skills/my-skill/SKILL.md'); + }); + + it('adds .cursor/ prefix to agent paths', () => { + const result = generateCursor(makeScan({ + agents: [{ + fileName: 'helper', + frontmatter: { name: 'helper', description: 'A helper' }, + body: 'Help the user', + sourcePath: '/fake/agents/helper.md', + }], + })); + const agent = result.files.find(f => f.type === 'agent'); + expect(agent?.path).toBe('.cursor/agents/helper.md'); + }); + + it('adds .cursor/ prefix to command paths', () => { + const result = generateCursor(makeScan({ + commands: [{ name: 'deploy', content: 'Deploy it', sourcePath: '/fake/commands/deploy.md' }], + })); + const cmd = result.files.find(f => f.type === 'command'); + expect(cmd?.path).toBe('.cursor/commands/deploy.md'); + }); + + it('adds .cursor/ prefix to instruction paths', () => { + const result = generateCursor(makeScan({ + instructions: [{ + fileName: 'testing.md', + content: 'Always test', + isRule: true, + sourcePath: '/fake/rules/testing.md', + }], + })); + const rule = result.files.find(f => f.type === 'instruction'); + expect(rule?.path).toBe('.cursor/rules/testing.mdc'); + }); + + it('adds .cursor/ prefix to mcp.json', () => { + const result = generateCursor(makeScan({ + mcp: { + servers: [{ name: 'fs', command: 'npx', args: ['-y', 'fs-server'] }], + sourcePath: '/fake/.mcp.json', + }, + })); + const mcp = result.files.find(f => f.type === 'mcp'); + expect(mcp?.path).toBe('.cursor/mcp.json'); + }); + + it('adds .cursor/ prefix to hooks.json', () => { + const result = generateCursor(makeScan({ + hooks: { + SessionStart: [{ + matcher: '', + hooks: [{ type: 'command', command: 'echo hello' }], + }], + }, + })); + const hook = result.files.find(f => f.type === 'hook'); + expect(hook?.path).toBe('.cursor/hooks.json'); + }); + + it('adds .cursor/ prefix to pluginFiles (scripts/)', () => { + const result = generateCursor(makeScan({ + pluginFiles: [{ relativePath: 'scripts/setup.sh', content: '#!/bin/bash' }], + })); + const script = result.files.find(f => f.type === 'resource'); + expect(script?.path).toBe('.cursor/scripts/setup.sh'); + }); + + it('does NOT add prefix to .cursor-plugin/ manifest paths', () => { + const result = generateCursor(makeScan()); + const manifest = result.files.find(f => f.path === '.cursor-plugin/plugin.json'); + expect(manifest).toBeDefined(); + expect(manifest?.path).toBe('.cursor-plugin/plugin.json'); + }); +}); diff --git a/src/__tests__/instructions.test.ts b/src/__tests__/instructions.test.ts index be07f2a..68c1f70 100644 --- a/src/__tests__/instructions.test.ts +++ b/src/__tests__/instructions.test.ts @@ -35,9 +35,9 @@ describe('mergeInstructions', () => { it('creates separate .mdc files for cursor', () => { const result = mergeInstructions([claudeMd, rule], 'cursor'); expect(result).toHaveLength(2); - expect(result[0].path).toBe('.cursor/rules/claude-instructions.mdc'); + expect(result[0].path).toBe('rules/claude-instructions.mdc'); expect(result[0].content).toContain('alwaysApply: true'); - expect(result[1].path).toBe('.cursor/rules/testing.mdc'); + expect(result[1].path).toBe('rules/testing.mdc'); expect(result[1].content).toContain('alwaysApply: true'); }); diff --git a/src/__tests__/mcp.test.ts b/src/__tests__/mcp.test.ts index f878f4b..5c62969 100644 --- a/src/__tests__/mcp.test.ts +++ b/src/__tests__/mcp.test.ts @@ -42,7 +42,7 @@ describe('convertMCP', () => { it('converts to cursor JSON format', () => { const result = convertMCP(sampleMCP, 'cursor'); - expect(result.path).toBe('.cursor/mcp.json'); + expect(result.path).toBe('mcp.json'); const data = JSON.parse(result.content); expect(data.mcpServers.filesystem.command).toBe('npx'); expect(data.mcpServers.github.url).toBe('https://api.github.com/mcp'); diff --git a/src/__tests__/pluginManifest.test.ts b/src/__tests__/pluginManifest.test.ts index b77545f..658d72d 100644 --- a/src/__tests__/pluginManifest.test.ts +++ b/src/__tests__/pluginManifest.test.ts @@ -236,7 +236,7 @@ describe('convertPluginManifestForCursor', () => { expect(manifest.name).toBe('my-plugin'); expect(manifest.displayName).toBe('My Plugin'); expect(manifest.logo).toBe('./assets/logo.png'); - expect(manifest.skills).toBe('./skills/'); + expect(manifest.skills).toBe('./.cursor/skills/'); }); it('falls back to interface.displayName when no top-level displayName', () => { diff --git a/src/__tests__/skill.test.ts b/src/__tests__/skill.test.ts index 573c868..590cc0d 100644 --- a/src/__tests__/skill.test.ts +++ b/src/__tests__/skill.test.ts @@ -33,7 +33,7 @@ describe('convertSkill', () => { it('converts to cursor path', () => { const result = convertSkill(sampleSkill, 'cursor'); - expect(result.path).toBe('.cursor/skills/test-skill/SKILL.md'); + expect(result.path).toBe('skills/test-skill/SKILL.md'); }); it('preserves name and description in frontmatter', () => { diff --git a/src/__tests__/superpowers-integration.test.ts b/src/__tests__/superpowers-integration.test.ts index 17e3150..ba50ee1 100644 --- a/src/__tests__/superpowers-integration.test.ts +++ b/src/__tests__/superpowers-integration.test.ts @@ -338,38 +338,37 @@ describe.skipIf(!repoExists)('superpowers plugin integration', () => { const manifest = JSON.parse(pluginJson!.content); expect(manifest.name).toBe('superpowers'); expect(manifest.version).toBe('5.0.5'); - expect(manifest.skills).toBe('./skills/'); - expect(manifest.agents).toBe('./agents/'); - expect(manifest.commands).toBe('./commands/'); + expect(manifest.skills).toBe('./.cursor/skills/'); + expect(manifest.agents).toBe('./.cursor/agents/'); + expect(manifest.commands).toBe('./.cursor/commands/'); // New fields from plugin.json passthrough - expect(manifest.hooks).toBe('./hooks/hooks-cursor.json'); + expect(manifest.hooks).toBe('./.cursor/hooks.json'); expect(manifest.author).toEqual({ name: 'Jesse Vincent', email: 'jesse@fsck.com' }); }); - it('remaps skill paths from .cursor/ to plugin root', () => { + it('adds .cursor/ prefix to skill paths', () => { const skillFiles = result.files.filter(f => f.type === 'skill'); for (const f of skillFiles) { - expect(f.path).toMatch(/^skills\//); - expect(f.path).not.toMatch(/^\.cursor\//); + expect(f.path).toMatch(/^\.cursor\/skills\//); } }); - it('remaps agent paths', () => { + it('adds .cursor/ prefix to agent paths', () => { const agentFiles = result.files.filter(f => f.type === 'agent'); expect(agentFiles).toHaveLength(1); - expect(agentFiles[0].path).toBe('agents/code-reviewer.md'); + expect(agentFiles[0].path).toBe('.cursor/agents/code-reviewer.md'); }); - it('remaps command paths', () => { + it('adds .cursor/ prefix to command paths', () => { const cmdFiles = result.files.filter(f => f.type === 'command'); expect(cmdFiles).toHaveLength(3); for (const f of cmdFiles) { - expect(f.path).toMatch(/^commands\//); + expect(f.path).toMatch(/^\.cursor\/commands\//); } }); - it('generates hooks/hooks-cursor.json with camelCase events', () => { - const hooksFile = result.files.find(f => f.path === 'hooks/hooks-cursor.json'); + it('generates .cursor/hooks.json with camelCase events', () => { + const hooksFile = result.files.find(f => f.path === '.cursor/hooks.json'); expect(hooksFile).toBeDefined(); const hooksData = JSON.parse(hooksFile!.content); expect(hooksData.version).toBe(1); @@ -379,7 +378,7 @@ describe.skipIf(!repoExists)('superpowers plugin integration', () => { }); it('cursor hooks use relative paths (no ${CLAUDE_PLUGIN_ROOT})', () => { - const hooksFile = result.files.find(f => f.path === 'hooks/hooks-cursor.json'); + const hooksFile = result.files.find(f => f.path === '.cursor/hooks.json'); expect(hooksFile).toBeDefined(); expect(hooksFile!.content).not.toContain('CLAUDE_PLUGIN_ROOT'); expect(hooksFile!.content).toContain('./hooks/'); diff --git a/src/converter/agent.ts b/src/converter/agent.ts index aa5cc11..f798646 100644 --- a/src/converter/agent.ts +++ b/src/converter/agent.ts @@ -107,7 +107,7 @@ function convertToCursor(agent: Agent): ConvertedFile { } const content = stringifyFrontmatter(fm, agent.body); - return { path: `.cursor/agents/${agent.fileName}.md`, content, type: 'agent' }; + return { path: `agents/${agent.fileName}.md`, content, type: 'agent' }; } // --- Antigravity: .gemini/agents/*.md (YAML frontmatter) --- diff --git a/src/converter/command.ts b/src/converter/command.ts index 32fe0df..7ee0129 100644 --- a/src/converter/command.ts +++ b/src/converter/command.ts @@ -40,7 +40,7 @@ function convertToOpenCode(command: Command): ConvertedFile { function convertToCursor(command: Command): ConvertedFile { return { - path: `.cursor/commands/${command.name}.md`, + path: `commands/${command.name}.md`, content: command.content, type: 'command', }; diff --git a/src/converter/hooks.ts b/src/converter/hooks.ts index 5f949ee..a9cd5e5 100644 --- a/src/converter/hooks.ts +++ b/src/converter/hooks.ts @@ -90,7 +90,7 @@ function convertCursorHooks(hooks: Hooks): HookReport { if (Object.keys(cursorHooks).length > 0) { const content = JSON.stringify({ version: 1, hooks: cursorHooks }, null, 2); converted.push({ - path: 'hooks/hooks-cursor.json', + path: 'hooks.json', content, type: 'hook', }); diff --git a/src/converter/instructions.ts b/src/converter/instructions.ts index a3d7a19..7024de0 100644 --- a/src/converter/instructions.ts +++ b/src/converter/instructions.ts @@ -42,7 +42,7 @@ function convertToCursor(instruction: Instruction): ConvertedFile { alwaysApply: true, }; return { - path: `.cursor/rules/${name}.mdc`, + path: `rules/${name}.mdc`, content: stringifyFrontmatter(frontmatter, instruction.content), type: 'instruction', }; @@ -54,7 +54,7 @@ function convertToCursor(instruction: Instruction): ConvertedFile { alwaysApply: true, }; return { - path: '.cursor/rules/claude-instructions.mdc', + path: 'rules/claude-instructions.mdc', content: stringifyFrontmatter(frontmatter, instruction.content), type: 'instruction', }; diff --git a/src/converter/mcp.ts b/src/converter/mcp.ts index c1b7130..fae198c 100644 --- a/src/converter/mcp.ts +++ b/src/converter/mcp.ts @@ -121,7 +121,7 @@ function convertToCursor(mcp: MCPConfig): ConvertedFile { const content = JSON.stringify({ mcpServers }, null, 2); return { - path: '.cursor/mcp.json', + path: 'mcp.json', content, type: 'mcp', }; diff --git a/src/converter/pluginManifest.ts b/src/converter/pluginManifest.ts index 0badbc1..27b74bb 100644 --- a/src/converter/pluginManifest.ts +++ b/src/converter/pluginManifest.ts @@ -29,12 +29,12 @@ const PLATFORM_PATHS: Record = { cursor: { pluginJson: '.cursor-plugin/plugin.json', marketplaceJson: '.cursor-plugin/marketplace.json', - skills: './skills/', - agents: './agents/', - commands: './commands/', - instructions: './rules/', - mcp: './mcp.json', - hooks: './hooks/hooks-cursor.json', + skills: './.cursor/skills/', + agents: './.cursor/agents/', + commands: './.cursor/commands/', + instructions: './.cursor/rules/', + mcp: './.cursor/mcp.json', + hooks: './.cursor/hooks.json', }, }; diff --git a/src/converter/skill.ts b/src/converter/skill.ts index 51a5ee4..65540bd 100644 --- a/src/converter/skill.ts +++ b/src/converter/skill.ts @@ -11,7 +11,7 @@ function getSkillOutputPath(platform: Platform, dirName: string): string { case 'opencode': return `.opencode/skills/${dirName}/SKILL.md`; case 'cursor': - return `.cursor/skills/${dirName}/SKILL.md`; + return `skills/${dirName}/SKILL.md`; case 'antigravity': return `.agent/skills/${dirName}/SKILL.md`; } diff --git a/src/writer/cursor.ts b/src/writer/cursor.ts index d050800..ba2be00 100644 --- a/src/writer/cursor.ts +++ b/src/writer/cursor.ts @@ -51,25 +51,23 @@ export function generateCursor(scan: ScanResult): ConvertResult { const meta = (scan as PluginScanResult).meta; files.push(convertPluginManifestForCursor(scan, meta)); - // Remap paths: .cursor/xxx → plugin format (skills/, agents/, etc.) + // Add .cursor/ prefix to resource paths (skip .cursor-plugin/ manifest files) for (const file of files) { - file.path = remapToPluginPath(file.path); + file.path = addCursorPrefix(file.path); } return { platform: 'cursor', files, warnings }; } /** - * Remap .cursor/ paths to plugin directory layout. - * .cursor/skills/X/SKILL.md → skills/X/SKILL.md - * .cursor/agents/X.md → agents/X.md - * .cursor/commands/X.md → commands/X.md - * .cursor/rules/X.mdc → rules/X.mdc - * .cursor/mcp.json → mcp.json + * Add .cursor/ prefix to resource paths. + * Converters output clean paths (skills/, agents/, rules/, mcp.json, etc.) + * and this function adds the .cursor/ directory prefix. + * Paths that already have a dot-prefix (.cursor-plugin/) are left unchanged. */ -function remapToPluginPath(filePath: string): string { - if (filePath.startsWith('.cursor/')) { - return filePath.slice('.cursor/'.length); +function addCursorPrefix(filePath: string): string { + if (filePath.startsWith('.')) { + return filePath; } - return filePath; + return `.cursor/${filePath}`; } From 86bf2ec68c417396755d8e4f0a916058db838cc7 Mon Sep 17 00:00:00 2001 From: danniel <1037897418@qq.com> Date: Mon, 6 Apr 2026 14:32:49 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=201=E3=80=81=E5=88=A4=E7=A9=BA?= =?UTF-8?q?=EF=BC=8C=E5=8F=AA=E6=9C=89meta=E5=AD=98=E5=9C=A8=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E6=89=8D=E8=BF=9B=E8=A1=8C=E8=BD=AC=E6=8D=A2=EF=BC=9B?= =?UTF-8?q?2=E3=80=81=E4=B8=8D=E4=BB=A5.cursor-plugin=20=E5=BC=80=E5=A4=B4?= =?UTF-8?q?=E6=97=B6=E6=B7=BB=E5=8A=A0=20./cursor=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/writer/cursor.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/writer/cursor.ts b/src/writer/cursor.ts index ba2be00..839be7f 100644 --- a/src/writer/cursor.ts +++ b/src/writer/cursor.ts @@ -48,8 +48,10 @@ export function generateCursor(scan: ScanResult): ConvertResult { } // Cursor plugin manifest generation - const meta = (scan as PluginScanResult).meta; - files.push(convertPluginManifestForCursor(scan, meta)); + if ('meta' in scan) { + const meta = (scan as PluginScanResult).meta; + files.push(convertPluginManifestForCursor(scan, meta)); + } // Add .cursor/ prefix to resource paths (skip .cursor-plugin/ manifest files) for (const file of files) { @@ -66,7 +68,7 @@ export function generateCursor(scan: ScanResult): ConvertResult { * Paths that already have a dot-prefix (.cursor-plugin/) are left unchanged. */ function addCursorPrefix(filePath: string): string { - if (filePath.startsWith('.')) { + if (filePath.startsWith('.cursor-plugin/')) { return filePath; } return `.cursor/${filePath}`;