@every-env/compound-plugin 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.cursor-plugin/marketplace.json +25 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +14 -8
  4. package/bun.lock +1 -0
  5. package/docs/brainstorms/2026-02-14-copilot-converter-target-brainstorm.md +117 -0
  6. package/docs/brainstorms/2026-02-17-copilot-skill-naming-brainstorm.md +30 -0
  7. package/docs/plans/2026-02-14-feat-add-copilot-converter-target-plan.md +328 -0
  8. package/docs/specs/copilot.md +122 -0
  9. package/package.json +1 -1
  10. package/plugins/coding-tutor/.cursor-plugin/plugin.json +21 -0
  11. package/plugins/compound-engineering/.claude-plugin/plugin.json +1 -1
  12. package/plugins/compound-engineering/.cursor-plugin/plugin.json +31 -0
  13. package/plugins/compound-engineering/.mcp.json +8 -0
  14. package/plugins/compound-engineering/CHANGELOG.md +10 -0
  15. package/plugins/compound-engineering/commands/lfg.md +3 -3
  16. package/plugins/compound-engineering/commands/slfg.md +2 -2
  17. package/plugins/compound-engineering/commands/workflows/plan.md +15 -1
  18. package/src/commands/install.ts +5 -1
  19. package/src/commands/sync.ts +8 -8
  20. package/src/converters/{claude-to-cursor.ts → claude-to-copilot.ts} +93 -49
  21. package/src/sync/{cursor.ts → copilot.ts} +36 -14
  22. package/src/targets/copilot.ts +48 -0
  23. package/src/targets/index.ts +9 -9
  24. package/src/types/copilot.ts +31 -0
  25. package/src/utils/frontmatter.ts +1 -1
  26. package/tests/copilot-converter.test.ts +467 -0
  27. package/tests/copilot-writer.test.ts +189 -0
  28. package/tests/sync-copilot.test.ts +148 -0
  29. package/src/targets/cursor.ts +0 -48
  30. package/src/types/cursor.ts +0 -29
  31. package/tests/cursor-converter.test.ts +0 -347
  32. package/tests/cursor-writer.test.ts +0 -137
  33. package/tests/sync-cursor.test.ts +0 -92
@@ -4,19 +4,21 @@ import type { ClaudeHomeConfig } from "../parsers/claude-home"
4
4
  import type { ClaudeMcpServer } from "../types/claude"
5
5
  import { forceSymlink, isValidSkillName } from "../utils/symlink"
6
6
 
7
- type CursorMcpServer = {
7
+ type CopilotMcpServer = {
8
+ type: string
8
9
  command?: string
9
10
  args?: string[]
10
11
  url?: string
12
+ tools: string[]
11
13
  env?: Record<string, string>
12
14
  headers?: Record<string, string>
13
15
  }
14
16
 
15
- type CursorMcpConfig = {
16
- mcpServers: Record<string, CursorMcpServer>
17
+ type CopilotMcpConfig = {
18
+ mcpServers: Record<string, CopilotMcpServer>
17
19
  }
18
20
 
19
- export async function syncToCursor(
21
+ export async function syncToCopilot(
20
22
  config: ClaudeHomeConfig,
21
23
  outputRoot: string,
22
24
  ): Promise<void> {
@@ -33,10 +35,10 @@ export async function syncToCursor(
33
35
  }
34
36
 
35
37
  if (Object.keys(config.mcpServers).length > 0) {
36
- const mcpPath = path.join(outputRoot, "mcp.json")
38
+ const mcpPath = path.join(outputRoot, "copilot-mcp-config.json")
37
39
  const existing = await readJsonSafe(mcpPath)
38
- const converted = convertMcpForCursor(config.mcpServers)
39
- const merged: CursorMcpConfig = {
40
+ const converted = convertMcpForCopilot(config.mcpServers)
41
+ const merged: CopilotMcpConfig = {
40
42
  mcpServers: {
41
43
  ...(existing.mcpServers ?? {}),
42
44
  ...converted,
@@ -46,10 +48,10 @@ export async function syncToCursor(
46
48
  }
47
49
  }
48
50
 
49
- async function readJsonSafe(filePath: string): Promise<Partial<CursorMcpConfig>> {
51
+ async function readJsonSafe(filePath: string): Promise<Partial<CopilotMcpConfig>> {
50
52
  try {
51
53
  const content = await fs.readFile(filePath, "utf-8")
52
- return JSON.parse(content) as Partial<CursorMcpConfig>
54
+ return JSON.parse(content) as Partial<CopilotMcpConfig>
53
55
  } catch (err) {
54
56
  if ((err as NodeJS.ErrnoException).code === "ENOENT") {
55
57
  return {}
@@ -58,21 +60,41 @@ async function readJsonSafe(filePath: string): Promise<Partial<CursorMcpConfig>>
58
60
  }
59
61
  }
60
62
 
61
- function convertMcpForCursor(
63
+ function convertMcpForCopilot(
62
64
  servers: Record<string, ClaudeMcpServer>,
63
- ): Record<string, CursorMcpServer> {
64
- const result: Record<string, CursorMcpServer> = {}
65
+ ): Record<string, CopilotMcpServer> {
66
+ const result: Record<string, CopilotMcpServer> = {}
65
67
  for (const [name, server] of Object.entries(servers)) {
66
- const entry: CursorMcpServer = {}
68
+ const entry: CopilotMcpServer = {
69
+ type: server.command ? "local" : "sse",
70
+ tools: ["*"],
71
+ }
72
+
67
73
  if (server.command) {
68
74
  entry.command = server.command
69
75
  if (server.args && server.args.length > 0) entry.args = server.args
70
- if (server.env && Object.keys(server.env).length > 0) entry.env = server.env
71
76
  } else if (server.url) {
72
77
  entry.url = server.url
73
78
  if (server.headers && Object.keys(server.headers).length > 0) entry.headers = server.headers
74
79
  }
80
+
81
+ if (server.env && Object.keys(server.env).length > 0) {
82
+ entry.env = prefixEnvVars(server.env)
83
+ }
84
+
75
85
  result[name] = entry
76
86
  }
77
87
  return result
78
88
  }
89
+
90
+ function prefixEnvVars(env: Record<string, string>): Record<string, string> {
91
+ const result: Record<string, string> = {}
92
+ for (const [key, value] of Object.entries(env)) {
93
+ if (key.startsWith("COPILOT_MCP_")) {
94
+ result[key] = value
95
+ } else {
96
+ result[`COPILOT_MCP_${key}`] = value
97
+ }
98
+ }
99
+ return result
100
+ }
@@ -0,0 +1,48 @@
1
+ import path from "path"
2
+ import { backupFile, copyDir, ensureDir, writeJson, writeText } from "../utils/files"
3
+ import type { CopilotBundle } from "../types/copilot"
4
+
5
+ export async function writeCopilotBundle(outputRoot: string, bundle: CopilotBundle): Promise<void> {
6
+ const paths = resolveCopilotPaths(outputRoot)
7
+ await ensureDir(paths.githubDir)
8
+
9
+ if (bundle.agents.length > 0) {
10
+ const agentsDir = path.join(paths.githubDir, "agents")
11
+ for (const agent of bundle.agents) {
12
+ await writeText(path.join(agentsDir, `${agent.name}.agent.md`), agent.content + "\n")
13
+ }
14
+ }
15
+
16
+ if (bundle.generatedSkills.length > 0) {
17
+ const skillsDir = path.join(paths.githubDir, "skills")
18
+ for (const skill of bundle.generatedSkills) {
19
+ await writeText(path.join(skillsDir, skill.name, "SKILL.md"), skill.content + "\n")
20
+ }
21
+ }
22
+
23
+ if (bundle.skillDirs.length > 0) {
24
+ const skillsDir = path.join(paths.githubDir, "skills")
25
+ for (const skill of bundle.skillDirs) {
26
+ await copyDir(skill.sourceDir, path.join(skillsDir, skill.name))
27
+ }
28
+ }
29
+
30
+ if (bundle.mcpConfig && Object.keys(bundle.mcpConfig).length > 0) {
31
+ const mcpPath = path.join(paths.githubDir, "copilot-mcp-config.json")
32
+ const backupPath = await backupFile(mcpPath)
33
+ if (backupPath) {
34
+ console.log(`Backed up existing copilot-mcp-config.json to ${backupPath}`)
35
+ }
36
+ await writeJson(mcpPath, { mcpServers: bundle.mcpConfig })
37
+ }
38
+ }
39
+
40
+ function resolveCopilotPaths(outputRoot: string) {
41
+ const base = path.basename(outputRoot)
42
+ // If already pointing at .github, write directly into it
43
+ if (base === ".github") {
44
+ return { githubDir: outputRoot }
45
+ }
46
+ // Otherwise nest under .github
47
+ return { githubDir: path.join(outputRoot, ".github") }
48
+ }
@@ -2,20 +2,20 @@ import type { ClaudePlugin } from "../types/claude"
2
2
  import type { OpenCodeBundle } from "../types/opencode"
3
3
  import type { CodexBundle } from "../types/codex"
4
4
  import type { DroidBundle } from "../types/droid"
5
- import type { CursorBundle } from "../types/cursor"
6
5
  import type { PiBundle } from "../types/pi"
6
+ import type { CopilotBundle } from "../types/copilot"
7
7
  import type { GeminiBundle } from "../types/gemini"
8
8
  import { convertClaudeToOpenCode, type ClaudeToOpenCodeOptions } from "../converters/claude-to-opencode"
9
9
  import { convertClaudeToCodex } from "../converters/claude-to-codex"
10
10
  import { convertClaudeToDroid } from "../converters/claude-to-droid"
11
- import { convertClaudeToCursor } from "../converters/claude-to-cursor"
12
11
  import { convertClaudeToPi } from "../converters/claude-to-pi"
12
+ import { convertClaudeToCopilot } from "../converters/claude-to-copilot"
13
13
  import { convertClaudeToGemini } from "../converters/claude-to-gemini"
14
14
  import { writeOpenCodeBundle } from "./opencode"
15
15
  import { writeCodexBundle } from "./codex"
16
16
  import { writeDroidBundle } from "./droid"
17
- import { writeCursorBundle } from "./cursor"
18
17
  import { writePiBundle } from "./pi"
18
+ import { writeCopilotBundle } from "./copilot"
19
19
  import { writeGeminiBundle } from "./gemini"
20
20
 
21
21
  export type TargetHandler<TBundle = unknown> = {
@@ -44,18 +44,18 @@ export const targets: Record<string, TargetHandler> = {
44
44
  convert: convertClaudeToDroid as TargetHandler<DroidBundle>["convert"],
45
45
  write: writeDroidBundle as TargetHandler<DroidBundle>["write"],
46
46
  },
47
- cursor: {
48
- name: "cursor",
49
- implemented: true,
50
- convert: convertClaudeToCursor as TargetHandler<CursorBundle>["convert"],
51
- write: writeCursorBundle as TargetHandler<CursorBundle>["write"],
52
- },
53
47
  pi: {
54
48
  name: "pi",
55
49
  implemented: true,
56
50
  convert: convertClaudeToPi as TargetHandler<PiBundle>["convert"],
57
51
  write: writePiBundle as TargetHandler<PiBundle>["write"],
58
52
  },
53
+ copilot: {
54
+ name: "copilot",
55
+ implemented: true,
56
+ convert: convertClaudeToCopilot as TargetHandler<CopilotBundle>["convert"],
57
+ write: writeCopilotBundle as TargetHandler<CopilotBundle>["write"],
58
+ },
59
59
  gemini: {
60
60
  name: "gemini",
61
61
  implemented: true,
@@ -0,0 +1,31 @@
1
+ export type CopilotAgent = {
2
+ name: string
3
+ content: string
4
+ }
5
+
6
+ export type CopilotGeneratedSkill = {
7
+ name: string
8
+ content: string
9
+ }
10
+
11
+ export type CopilotSkillDir = {
12
+ name: string
13
+ sourceDir: string
14
+ }
15
+
16
+ export type CopilotMcpServer = {
17
+ type: string
18
+ command?: string
19
+ args?: string[]
20
+ url?: string
21
+ tools: string[]
22
+ env?: Record<string, string>
23
+ headers?: Record<string, string>
24
+ }
25
+
26
+ export type CopilotBundle = {
27
+ agents: CopilotAgent[]
28
+ generatedSkills: CopilotGeneratedSkill[]
29
+ skillDirs: CopilotSkillDir[]
30
+ mcpConfig?: Record<string, CopilotMcpServer>
31
+ }
@@ -58,7 +58,7 @@ function formatYamlValue(value: unknown): string {
58
58
  if (raw.includes("\n")) {
59
59
  return `|\n${raw.split("\n").map((line) => ` ${line}`).join("\n")}`
60
60
  }
61
- if (raw.includes(":") || raw.startsWith("[") || raw.startsWith("{")) {
61
+ if (raw.includes(":") || raw.startsWith("[") || raw.startsWith("{") || raw === "*") {
62
62
  return JSON.stringify(raw)
63
63
  }
64
64
  return raw