@every-env/compound-plugin 0.7.0 → 0.9.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.
- package/.cursor-plugin/marketplace.json +25 -0
- package/CHANGELOG.md +21 -0
- package/README.md +18 -8
- package/bun.lock +1 -0
- package/docs/brainstorms/2026-02-14-copilot-converter-target-brainstorm.md +117 -0
- package/docs/brainstorms/2026-02-17-copilot-skill-naming-brainstorm.md +30 -0
- package/docs/plans/2026-02-14-feat-add-copilot-converter-target-plan.md +328 -0
- package/docs/specs/copilot.md +122 -0
- package/docs/specs/kiro.md +171 -0
- package/package.json +1 -1
- package/plugins/coding-tutor/.cursor-plugin/plugin.json +21 -0
- package/plugins/compound-engineering/.claude-plugin/plugin.json +1 -1
- package/plugins/compound-engineering/.cursor-plugin/plugin.json +31 -0
- package/plugins/compound-engineering/.mcp.json +8 -0
- package/plugins/compound-engineering/CHANGELOG.md +10 -0
- package/plugins/compound-engineering/commands/lfg.md +3 -3
- package/plugins/compound-engineering/commands/slfg.md +2 -2
- package/plugins/compound-engineering/commands/workflows/plan.md +15 -1
- package/src/commands/convert.ts +2 -1
- package/src/commands/install.ts +9 -1
- package/src/commands/sync.ts +8 -8
- package/src/converters/{claude-to-cursor.ts → claude-to-copilot.ts} +93 -49
- package/src/converters/claude-to-kiro.ts +262 -0
- package/src/sync/{cursor.ts → copilot.ts} +36 -14
- package/src/targets/copilot.ts +48 -0
- package/src/targets/index.ts +18 -9
- package/src/targets/kiro.ts +122 -0
- package/src/types/copilot.ts +31 -0
- package/src/types/kiro.ts +44 -0
- package/src/utils/frontmatter.ts +1 -1
- package/tests/copilot-converter.test.ts +467 -0
- package/tests/copilot-writer.test.ts +189 -0
- package/tests/kiro-converter.test.ts +381 -0
- package/tests/kiro-writer.test.ts +273 -0
- package/tests/sync-copilot.test.ts +148 -0
- package/src/targets/cursor.ts +0 -48
- package/src/types/cursor.ts +0 -29
- package/tests/cursor-converter.test.ts +0 -347
- package/tests/cursor-writer.test.ts +0 -137
- package/tests/sync-cursor.test.ts +0 -92
package/src/targets/index.ts
CHANGED
|
@@ -2,21 +2,24 @@ 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
|
+
import type { KiroBundle } from "../types/kiro"
|
|
8
9
|
import { convertClaudeToOpenCode, type ClaudeToOpenCodeOptions } from "../converters/claude-to-opencode"
|
|
9
10
|
import { convertClaudeToCodex } from "../converters/claude-to-codex"
|
|
10
11
|
import { convertClaudeToDroid } from "../converters/claude-to-droid"
|
|
11
|
-
import { convertClaudeToCursor } from "../converters/claude-to-cursor"
|
|
12
12
|
import { convertClaudeToPi } from "../converters/claude-to-pi"
|
|
13
|
+
import { convertClaudeToCopilot } from "../converters/claude-to-copilot"
|
|
13
14
|
import { convertClaudeToGemini } from "../converters/claude-to-gemini"
|
|
15
|
+
import { convertClaudeToKiro } from "../converters/claude-to-kiro"
|
|
14
16
|
import { writeOpenCodeBundle } from "./opencode"
|
|
15
17
|
import { writeCodexBundle } from "./codex"
|
|
16
18
|
import { writeDroidBundle } from "./droid"
|
|
17
|
-
import { writeCursorBundle } from "./cursor"
|
|
18
19
|
import { writePiBundle } from "./pi"
|
|
20
|
+
import { writeCopilotBundle } from "./copilot"
|
|
19
21
|
import { writeGeminiBundle } from "./gemini"
|
|
22
|
+
import { writeKiroBundle } from "./kiro"
|
|
20
23
|
|
|
21
24
|
export type TargetHandler<TBundle = unknown> = {
|
|
22
25
|
name: string
|
|
@@ -44,22 +47,28 @@ export const targets: Record<string, TargetHandler> = {
|
|
|
44
47
|
convert: convertClaudeToDroid as TargetHandler<DroidBundle>["convert"],
|
|
45
48
|
write: writeDroidBundle as TargetHandler<DroidBundle>["write"],
|
|
46
49
|
},
|
|
47
|
-
cursor: {
|
|
48
|
-
name: "cursor",
|
|
49
|
-
implemented: true,
|
|
50
|
-
convert: convertClaudeToCursor as TargetHandler<CursorBundle>["convert"],
|
|
51
|
-
write: writeCursorBundle as TargetHandler<CursorBundle>["write"],
|
|
52
|
-
},
|
|
53
50
|
pi: {
|
|
54
51
|
name: "pi",
|
|
55
52
|
implemented: true,
|
|
56
53
|
convert: convertClaudeToPi as TargetHandler<PiBundle>["convert"],
|
|
57
54
|
write: writePiBundle as TargetHandler<PiBundle>["write"],
|
|
58
55
|
},
|
|
56
|
+
copilot: {
|
|
57
|
+
name: "copilot",
|
|
58
|
+
implemented: true,
|
|
59
|
+
convert: convertClaudeToCopilot as TargetHandler<CopilotBundle>["convert"],
|
|
60
|
+
write: writeCopilotBundle as TargetHandler<CopilotBundle>["write"],
|
|
61
|
+
},
|
|
59
62
|
gemini: {
|
|
60
63
|
name: "gemini",
|
|
61
64
|
implemented: true,
|
|
62
65
|
convert: convertClaudeToGemini as TargetHandler<GeminiBundle>["convert"],
|
|
63
66
|
write: writeGeminiBundle as TargetHandler<GeminiBundle>["write"],
|
|
64
67
|
},
|
|
68
|
+
kiro: {
|
|
69
|
+
name: "kiro",
|
|
70
|
+
implemented: true,
|
|
71
|
+
convert: convertClaudeToKiro as TargetHandler<KiroBundle>["convert"],
|
|
72
|
+
write: writeKiroBundle as TargetHandler<KiroBundle>["write"],
|
|
73
|
+
},
|
|
65
74
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import { backupFile, copyDir, ensureDir, pathExists, readJson, writeJson, writeText } from "../utils/files"
|
|
3
|
+
import type { KiroBundle } from "../types/kiro"
|
|
4
|
+
|
|
5
|
+
export async function writeKiroBundle(outputRoot: string, bundle: KiroBundle): Promise<void> {
|
|
6
|
+
const paths = resolveKiroPaths(outputRoot)
|
|
7
|
+
await ensureDir(paths.kiroDir)
|
|
8
|
+
|
|
9
|
+
// Write agents
|
|
10
|
+
if (bundle.agents.length > 0) {
|
|
11
|
+
for (const agent of bundle.agents) {
|
|
12
|
+
// Validate name doesn't escape agents directory
|
|
13
|
+
validatePathSafe(agent.name, "agent")
|
|
14
|
+
|
|
15
|
+
// Write agent JSON config
|
|
16
|
+
await writeJson(
|
|
17
|
+
path.join(paths.agentsDir, `${agent.name}.json`),
|
|
18
|
+
agent.config,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
// Write agent prompt file
|
|
22
|
+
await writeText(
|
|
23
|
+
path.join(paths.agentsDir, "prompts", `${agent.name}.md`),
|
|
24
|
+
agent.promptContent + "\n",
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Write generated skills (from commands)
|
|
30
|
+
if (bundle.generatedSkills.length > 0) {
|
|
31
|
+
for (const skill of bundle.generatedSkills) {
|
|
32
|
+
validatePathSafe(skill.name, "skill")
|
|
33
|
+
await writeText(
|
|
34
|
+
path.join(paths.skillsDir, skill.name, "SKILL.md"),
|
|
35
|
+
skill.content + "\n",
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Copy skill directories (pass-through)
|
|
41
|
+
if (bundle.skillDirs.length > 0) {
|
|
42
|
+
for (const skill of bundle.skillDirs) {
|
|
43
|
+
validatePathSafe(skill.name, "skill directory")
|
|
44
|
+
const destDir = path.join(paths.skillsDir, skill.name)
|
|
45
|
+
|
|
46
|
+
// Validate destination doesn't escape skills directory
|
|
47
|
+
const resolvedDest = path.resolve(destDir)
|
|
48
|
+
if (!resolvedDest.startsWith(path.resolve(paths.skillsDir))) {
|
|
49
|
+
console.warn(`Warning: Skill name "${skill.name}" escapes .kiro/skills/. Skipping.`)
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await copyDir(skill.sourceDir, destDir)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Write steering files
|
|
58
|
+
if (bundle.steeringFiles.length > 0) {
|
|
59
|
+
for (const file of bundle.steeringFiles) {
|
|
60
|
+
validatePathSafe(file.name, "steering file")
|
|
61
|
+
await writeText(
|
|
62
|
+
path.join(paths.steeringDir, `${file.name}.md`),
|
|
63
|
+
file.content + "\n",
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Write MCP servers to mcp.json
|
|
69
|
+
if (Object.keys(bundle.mcpServers).length > 0) {
|
|
70
|
+
const mcpPath = path.join(paths.settingsDir, "mcp.json")
|
|
71
|
+
const backupPath = await backupFile(mcpPath)
|
|
72
|
+
if (backupPath) {
|
|
73
|
+
console.log(`Backed up existing mcp.json to ${backupPath}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Merge with existing mcp.json if present
|
|
77
|
+
let existingConfig: Record<string, unknown> = {}
|
|
78
|
+
if (await pathExists(mcpPath)) {
|
|
79
|
+
try {
|
|
80
|
+
existingConfig = await readJson<Record<string, unknown>>(mcpPath)
|
|
81
|
+
} catch {
|
|
82
|
+
console.warn("Warning: existing mcp.json could not be parsed and will be replaced.")
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const existingServers =
|
|
87
|
+
existingConfig.mcpServers && typeof existingConfig.mcpServers === "object"
|
|
88
|
+
? (existingConfig.mcpServers as Record<string, unknown>)
|
|
89
|
+
: {}
|
|
90
|
+
const merged = { ...existingConfig, mcpServers: { ...existingServers, ...bundle.mcpServers } }
|
|
91
|
+
await writeJson(mcpPath, merged)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveKiroPaths(outputRoot: string) {
|
|
96
|
+
const base = path.basename(outputRoot)
|
|
97
|
+
// If already pointing at .kiro, write directly into it
|
|
98
|
+
if (base === ".kiro") {
|
|
99
|
+
return {
|
|
100
|
+
kiroDir: outputRoot,
|
|
101
|
+
agentsDir: path.join(outputRoot, "agents"),
|
|
102
|
+
skillsDir: path.join(outputRoot, "skills"),
|
|
103
|
+
steeringDir: path.join(outputRoot, "steering"),
|
|
104
|
+
settingsDir: path.join(outputRoot, "settings"),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Otherwise nest under .kiro
|
|
108
|
+
const kiroDir = path.join(outputRoot, ".kiro")
|
|
109
|
+
return {
|
|
110
|
+
kiroDir,
|
|
111
|
+
agentsDir: path.join(kiroDir, "agents"),
|
|
112
|
+
skillsDir: path.join(kiroDir, "skills"),
|
|
113
|
+
steeringDir: path.join(kiroDir, "steering"),
|
|
114
|
+
settingsDir: path.join(kiroDir, "settings"),
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validatePathSafe(name: string, label: string): void {
|
|
119
|
+
if (name.includes("..") || name.includes("/") || name.includes("\\")) {
|
|
120
|
+
throw new Error(`${label} name contains unsafe path characters: ${name}`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type KiroAgent = {
|
|
2
|
+
name: string
|
|
3
|
+
config: KiroAgentConfig
|
|
4
|
+
promptContent: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type KiroAgentConfig = {
|
|
8
|
+
name: string
|
|
9
|
+
description: string
|
|
10
|
+
prompt: `file://${string}`
|
|
11
|
+
tools: ["*"]
|
|
12
|
+
resources: string[]
|
|
13
|
+
includeMcpJson: true
|
|
14
|
+
welcomeMessage?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type KiroSkill = {
|
|
18
|
+
name: string
|
|
19
|
+
content: string // Full SKILL.md with YAML frontmatter
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type KiroSkillDir = {
|
|
23
|
+
name: string
|
|
24
|
+
sourceDir: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type KiroSteeringFile = {
|
|
28
|
+
name: string
|
|
29
|
+
content: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type KiroMcpServer = {
|
|
33
|
+
command: string
|
|
34
|
+
args?: string[]
|
|
35
|
+
env?: Record<string, string>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type KiroBundle = {
|
|
39
|
+
agents: KiroAgent[]
|
|
40
|
+
generatedSkills: KiroSkill[]
|
|
41
|
+
skillDirs: KiroSkillDir[]
|
|
42
|
+
steeringFiles: KiroSteeringFile[]
|
|
43
|
+
mcpServers: Record<string, KiroMcpServer>
|
|
44
|
+
}
|
package/src/utils/frontmatter.ts
CHANGED
|
@@ -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
|