@every-env/compound-plugin 0.5.2 → 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 (56) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.cursor-plugin/marketplace.json +25 -0
  3. package/CHANGELOG.md +47 -0
  4. package/README.md +29 -6
  5. package/bun.lock +1 -0
  6. package/docs/brainstorms/2026-02-14-copilot-converter-target-brainstorm.md +117 -0
  7. package/docs/brainstorms/2026-02-17-copilot-skill-naming-brainstorm.md +30 -0
  8. package/docs/plans/2026-02-14-feat-add-copilot-converter-target-plan.md +328 -0
  9. package/docs/plans/2026-02-14-feat-add-gemini-cli-target-provider-plan.md +370 -0
  10. package/docs/specs/copilot.md +122 -0
  11. package/docs/specs/gemini.md +122 -0
  12. package/package.json +1 -1
  13. package/plugins/coding-tutor/.cursor-plugin/plugin.json +21 -0
  14. package/plugins/compound-engineering/.claude-plugin/plugin.json +1 -1
  15. package/plugins/compound-engineering/.cursor-plugin/plugin.json +31 -0
  16. package/plugins/compound-engineering/.mcp.json +8 -0
  17. package/plugins/compound-engineering/CHANGELOG.md +27 -0
  18. package/plugins/compound-engineering/commands/lfg.md +3 -3
  19. package/plugins/compound-engineering/commands/slfg.md +2 -2
  20. package/plugins/compound-engineering/commands/workflows/plan.md +18 -1
  21. package/plugins/compound-engineering/commands/workflows/work.md +8 -1
  22. package/src/commands/convert.ts +14 -25
  23. package/src/commands/install.ts +27 -25
  24. package/src/commands/sync.ts +44 -21
  25. package/src/converters/{claude-to-cursor.ts → claude-to-copilot.ts} +93 -49
  26. package/src/converters/claude-to-gemini.ts +193 -0
  27. package/src/converters/claude-to-opencode.ts +16 -0
  28. package/src/converters/claude-to-pi.ts +205 -0
  29. package/src/sync/copilot.ts +100 -0
  30. package/src/sync/droid.ts +21 -0
  31. package/src/sync/pi.ts +88 -0
  32. package/src/targets/copilot.ts +48 -0
  33. package/src/targets/gemini.ts +68 -0
  34. package/src/targets/index.ts +25 -7
  35. package/src/targets/pi.ts +131 -0
  36. package/src/templates/pi/compat-extension.ts +452 -0
  37. package/src/types/copilot.ts +31 -0
  38. package/src/types/gemini.ts +29 -0
  39. package/src/types/pi.ts +40 -0
  40. package/src/utils/frontmatter.ts +1 -1
  41. package/src/utils/resolve-home.ts +17 -0
  42. package/tests/cli.test.ts +76 -0
  43. package/tests/converter.test.ts +29 -0
  44. package/tests/copilot-converter.test.ts +467 -0
  45. package/tests/copilot-writer.test.ts +189 -0
  46. package/tests/gemini-converter.test.ts +373 -0
  47. package/tests/gemini-writer.test.ts +181 -0
  48. package/tests/pi-converter.test.ts +116 -0
  49. package/tests/pi-writer.test.ts +99 -0
  50. package/tests/sync-copilot.test.ts +148 -0
  51. package/tests/sync-droid.test.ts +57 -0
  52. package/tests/sync-pi.test.ts +68 -0
  53. package/src/targets/cursor.ts +0 -48
  54. package/src/types/cursor.ts +0 -29
  55. package/tests/cursor-converter.test.ts +0 -347
  56. package/tests/cursor-writer.test.ts +0 -137
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "context7": {
4
+ "type": "http",
5
+ "url": "https://mcp.context7.com/mcp"
6
+ }
7
+ }
8
+ }
@@ -5,6 +5,33 @@ All notable changes to the compound-engineering plugin will be documented in thi
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.35.0] - 2026-02-17
9
+
10
+ ### Fixed
11
+
12
+ - **`/lfg` and `/slfg` first-run failures** — Made ralph-loop step optional with graceful fallback when `ralph-wiggum` skill is not installed (#154). Added explicit "do not stop" instruction across all steps (#134).
13
+ - **`/workflows:plan` not writing file in pipeline** — Added mandatory "Write Plan File" step with explicit Write tool instructions before Post-Generation Options. The file is now always written to disk before any interactive prompts (#155). Also adds pipeline-mode note to skip AskUserQuestion calls when invoked from LFG/SLFG (#134).
14
+ - **Agent namespace typo in `/workflows:plan`** — `Task spec-flow-analyzer(...)` now uses the full qualified name `Task compound-engineering:workflow:spec-flow-analyzer(...)` to prevent Claude from prepending the wrong `workflows:` prefix (#193).
15
+
16
+ ---
17
+
18
+ ## [2.34.0] - 2026-02-14
19
+
20
+ ### Added
21
+
22
+ - **Gemini CLI target** — New converter target for [Gemini CLI](https://github.com/google-gemini/gemini-cli). Install with `--to gemini` to convert agents to `.gemini/skills/*/SKILL.md`, commands to `.gemini/commands/*.toml` (TOML format with `description` + `prompt`), and MCP servers to `.gemini/settings.json`. Skills pass through unchanged (identical SKILL.md standard). Namespaced commands create directory structure (`workflows:plan` → `commands/workflows/plan.toml`). 29 new tests. ([#190](https://github.com/EveryInc/compound-engineering-plugin/pull/190))
23
+
24
+ ---
25
+
26
+ ## [2.33.1] - 2026-02-13
27
+
28
+ ### Changed
29
+
30
+ - **`/workflows:plan` command** - All plan templates now include `status: active` in YAML frontmatter. Plans are created with `status: active` and marked `status: completed` when work finishes.
31
+ - **`/workflows:work` command** - Phase 4 now updates plan frontmatter from `status: active` to `status: completed` after shipping. Agents can grep for status to distinguish current vs historical plans.
32
+
33
+ ---
34
+
8
35
  ## [2.33.0] - 2026-02-12
9
36
 
10
37
  ### Added
@@ -5,9 +5,9 @@ argument-hint: "[feature description]"
5
5
  disable-model-invocation: true
6
6
  ---
7
7
 
8
- Run these slash commands in order. Do not do anything else.
8
+ Run these slash commands in order. Do not do anything else. Do not stop between steps — complete every step through to the end.
9
9
 
10
- 1. `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`
10
+ 1. **Optional:** If the `ralph-wiggum` skill is available, run `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`. If not available or it fails, skip and continue to step 2 immediately.
11
11
  2. `/workflows:plan $ARGUMENTS`
12
12
  3. `/compound-engineering:deepen-plan`
13
13
  4. `/workflows:work`
@@ -17,4 +17,4 @@ Run these slash commands in order. Do not do anything else.
17
17
  8. `/compound-engineering:feature-video`
18
18
  9. Output `<promise>DONE</promise>` when video is in PR
19
19
 
20
- Start with step 1 now.
20
+ Start with step 2 now (or step 1 if ralph-wiggum is available).
@@ -5,11 +5,11 @@ argument-hint: "[feature description]"
5
5
  disable-model-invocation: true
6
6
  ---
7
7
 
8
- Swarm-enabled LFG. Run these steps in order, parallelizing where indicated.
8
+ Swarm-enabled LFG. Run these steps in order, parallelizing where indicated. Do not stop between steps — complete every step through to the end.
9
9
 
10
10
  ## Sequential Phase
11
11
 
12
- 1. `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`
12
+ 1. **Optional:** If the `ralph-wiggum` skill is available, run `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`. If not available or it fails, skip and continue to step 2 immediately.
13
13
  2. `/workflows:plan $ARGUMENTS`
14
14
  3. `/compound-engineering:deepen-plan`
15
15
  4. `/workflows:work` — **Use swarm mode**: Make a Task list and launch an army of agent swarm subagents to build the plan
@@ -150,7 +150,7 @@ Think like a product manager - what would make this issue clear and actionable?
150
150
 
151
151
  After planning the issue structure, run SpecFlow Analyzer to validate and refine the feature specification:
152
152
 
153
- - Task spec-flow-analyzer(feature_description, research_findings)
153
+ - Task compound-engineering:workflow:spec-flow-analyzer(feature_description, research_findings)
154
154
 
155
155
  **SpecFlow Analyzer Output:**
156
156
 
@@ -178,6 +178,7 @@ Select how comprehensive you want the issue to be, simpler is mostly better.
178
178
  ---
179
179
  title: [Issue Title]
180
180
  type: [feat|fix|refactor]
181
+ status: active
181
182
  date: YYYY-MM-DD
182
183
  ---
183
184
 
@@ -230,6 +231,7 @@ end
230
231
  ---
231
232
  title: [Issue Title]
232
233
  type: [feat|fix|refactor]
234
+ status: active
233
235
  date: YYYY-MM-DD
234
236
  ---
235
237
 
@@ -294,6 +296,7 @@ date: YYYY-MM-DD
294
296
  ---
295
297
  title: [Issue Title]
296
298
  type: [feat|fix|refactor]
299
+ status: active
297
300
  date: YYYY-MM-DD
298
301
  ---
299
302
 
@@ -472,6 +475,20 @@ end
472
475
  - [ ] Add names of files in pseudo code examples and todo lists
473
476
  - [ ] Add an ERD mermaid diagram if applicable for new model changes
474
477
 
478
+ ## Write Plan File
479
+
480
+ **REQUIRED: Write the plan file to disk before presenting any options.**
481
+
482
+ ```bash
483
+ mkdir -p docs/plans/
484
+ ```
485
+
486
+ Use the Write tool to save the complete plan to `docs/plans/YYYY-MM-DD-<type>-<descriptive-name>-plan.md`. This step is mandatory and cannot be skipped — even when running as part of LFG/SLFG or other automated pipelines.
487
+
488
+ Confirm: "Plan written to docs/plans/[filename]"
489
+
490
+ **Pipeline mode:** If invoked from an automated workflow (LFG, SLFG, or any `disable-model-invocation` context), skip all AskUserQuestion calls. Make decisions automatically and proceed to writing the plan without interactive prompts.
491
+
475
492
  ## Output Format
476
493
 
477
494
  **Filename:** Use the date and kebab-case filename from Step 2 Title & Categorization.
@@ -297,7 +297,14 @@ This command takes a work document (plan, specification, or todo file) and execu
297
297
  )"
298
298
  ```
299
299
 
300
- 4. **Notify User**
300
+ 4. **Update Plan Status**
301
+
302
+ If the input document has YAML frontmatter with a `status` field, update it to `completed`:
303
+ ```
304
+ status: active → status: completed
305
+ ```
306
+
307
+ 5. **Notify User**
301
308
  - Summarize what was completed
302
309
  - Link to PR
303
310
  - Note any follow-up work needed
@@ -5,6 +5,7 @@ import { loadClaudePlugin } from "../parsers/claude"
5
5
  import { targets } from "../targets"
6
6
  import type { PermissionMode } from "../converters/claude-to-opencode"
7
7
  import { ensureCodexAgentsFile } from "../utils/codex-agents"
8
+ import { expandHome, resolveTargetHome } from "../utils/resolve-home"
8
9
 
9
10
  const permissionModes: PermissionMode[] = ["none", "broad", "from-commands"]
10
11
 
@@ -22,7 +23,7 @@ export default defineCommand({
22
23
  to: {
23
24
  type: "string",
24
25
  default: "opencode",
25
- description: "Target format (opencode | codex | droid | cursor)",
26
+ description: "Target format (opencode | codex | droid | cursor | pi | gemini)",
26
27
  },
27
28
  output: {
28
29
  type: "string",
@@ -34,6 +35,11 @@ export default defineCommand({
34
35
  alias: "codex-home",
35
36
  description: "Write Codex output to this .codex root (ex: ~/.codex)",
36
37
  },
38
+ piHome: {
39
+ type: "string",
40
+ alias: "pi-home",
41
+ description: "Write Pi output to this Pi root (ex: ~/.pi/agent or ./.pi)",
42
+ },
37
43
  also: {
38
44
  type: "string",
39
45
  description: "Comma-separated extra targets to generate (ex: codex)",
@@ -72,7 +78,8 @@ export default defineCommand({
72
78
 
73
79
  const plugin = await loadClaudePlugin(String(args.source))
74
80
  const outputRoot = resolveOutputRoot(args.output)
75
- const codexHome = resolveCodexRoot(args.codexHome)
81
+ const codexHome = resolveTargetHome(args.codexHome, path.join(os.homedir(), ".codex"))
82
+ const piHome = resolveTargetHome(args.piHome, path.join(os.homedir(), ".pi", "agent"))
76
83
 
77
84
  const options = {
78
85
  agentMode: String(args.agentMode) === "primary" ? "primary" : "subagent",
@@ -80,7 +87,7 @@ export default defineCommand({
80
87
  permissions: permissions as PermissionMode,
81
88
  }
82
89
 
83
- const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome)
90
+ const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome)
84
91
  const bundle = target.convert(plugin, options)
85
92
  if (!bundle) {
86
93
  throw new Error(`Target ${targetName} did not return a bundle.`)
@@ -106,7 +113,7 @@ export default defineCommand({
106
113
  console.warn(`Skipping ${extra}: no output returned.`)
107
114
  continue
108
115
  }
109
- const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome)
116
+ const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, piHome)
110
117
  await handler.write(extraRoot, extraBundle)
111
118
  console.log(`Converted ${plugin.manifest.name} to ${extra} at ${extraRoot}`)
112
119
  }
@@ -125,26 +132,6 @@ function parseExtraTargets(value: unknown): string[] {
125
132
  .filter(Boolean)
126
133
  }
127
134
 
128
- function resolveCodexHome(value: unknown): string | null {
129
- if (!value) return null
130
- const raw = String(value).trim()
131
- if (!raw) return null
132
- const expanded = expandHome(raw)
133
- return path.resolve(expanded)
134
- }
135
-
136
- function resolveCodexRoot(value: unknown): string {
137
- return resolveCodexHome(value) ?? path.join(os.homedir(), ".codex")
138
- }
139
-
140
- function expandHome(value: string): string {
141
- if (value === "~") return os.homedir()
142
- if (value.startsWith(`~${path.sep}`)) {
143
- return path.join(os.homedir(), value.slice(2))
144
- }
145
- return value
146
- }
147
-
148
135
  function resolveOutputRoot(value: unknown): string {
149
136
  if (value && String(value).trim()) {
150
137
  const expanded = expandHome(String(value).trim())
@@ -153,9 +140,11 @@ function resolveOutputRoot(value: unknown): string {
153
140
  return process.cwd()
154
141
  }
155
142
 
156
- function resolveTargetOutputRoot(targetName: string, outputRoot: string, codexHome: string): string {
143
+ function resolveTargetOutputRoot(targetName: string, outputRoot: string, codexHome: string, piHome: string): string {
157
144
  if (targetName === "codex") return codexHome
145
+ if (targetName === "pi") return piHome
158
146
  if (targetName === "droid") return path.join(os.homedir(), ".factory")
159
147
  if (targetName === "cursor") return path.join(outputRoot, ".cursor")
148
+ if (targetName === "gemini") return path.join(outputRoot, ".gemini")
160
149
  return outputRoot
161
150
  }
@@ -7,6 +7,7 @@ import { targets } from "../targets"
7
7
  import { pathExists } from "../utils/files"
8
8
  import type { PermissionMode } from "../converters/claude-to-opencode"
9
9
  import { ensureCodexAgentsFile } from "../utils/codex-agents"
10
+ import { expandHome, resolveTargetHome } from "../utils/resolve-home"
10
11
 
11
12
  const permissionModes: PermissionMode[] = ["none", "broad", "from-commands"]
12
13
 
@@ -24,7 +25,7 @@ export default defineCommand({
24
25
  to: {
25
26
  type: "string",
26
27
  default: "opencode",
27
- description: "Target format (opencode | codex | droid | cursor)",
28
+ description: "Target format (opencode | codex | droid | cursor | pi | copilot | gemini)",
28
29
  },
29
30
  output: {
30
31
  type: "string",
@@ -36,6 +37,11 @@ export default defineCommand({
36
37
  alias: "codex-home",
37
38
  description: "Write Codex output to this .codex root (ex: ~/.codex)",
38
39
  },
40
+ piHome: {
41
+ type: "string",
42
+ alias: "pi-home",
43
+ description: "Write Pi output to this Pi root (ex: ~/.pi/agent or ./.pi)",
44
+ },
39
45
  also: {
40
46
  type: "string",
41
47
  description: "Comma-separated extra targets to generate (ex: codex)",
@@ -76,7 +82,8 @@ export default defineCommand({
76
82
  try {
77
83
  const plugin = await loadClaudePlugin(resolvedPlugin.path)
78
84
  const outputRoot = resolveOutputRoot(args.output)
79
- const codexHome = resolveCodexRoot(args.codexHome)
85
+ const codexHome = resolveTargetHome(args.codexHome, path.join(os.homedir(), ".codex"))
86
+ const piHome = resolveTargetHome(args.piHome, path.join(os.homedir(), ".pi", "agent"))
80
87
 
81
88
  const options = {
82
89
  agentMode: String(args.agentMode) === "primary" ? "primary" : "subagent",
@@ -89,7 +96,7 @@ export default defineCommand({
89
96
  throw new Error(`Target ${targetName} did not return a bundle.`)
90
97
  }
91
98
  const hasExplicitOutput = Boolean(args.output && String(args.output).trim())
92
- const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, hasExplicitOutput)
99
+ const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome, hasExplicitOutput)
93
100
  await target.write(primaryOutputRoot, bundle)
94
101
  console.log(`Installed ${plugin.manifest.name} to ${primaryOutputRoot}`)
95
102
 
@@ -110,7 +117,7 @@ export default defineCommand({
110
117
  console.warn(`Skipping ${extra}: no output returned.`)
111
118
  continue
112
119
  }
113
- const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, hasExplicitOutput)
120
+ const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, piHome, hasExplicitOutput)
114
121
  await handler.write(extraRoot, extraBundle)
115
122
  console.log(`Installed ${plugin.manifest.name} to ${extraRoot}`)
116
123
  }
@@ -152,26 +159,6 @@ function parseExtraTargets(value: unknown): string[] {
152
159
  .filter(Boolean)
153
160
  }
154
161
 
155
- function resolveCodexHome(value: unknown): string | null {
156
- if (!value) return null
157
- const raw = String(value).trim()
158
- if (!raw) return null
159
- const expanded = expandHome(raw)
160
- return path.resolve(expanded)
161
- }
162
-
163
- function resolveCodexRoot(value: unknown): string {
164
- return resolveCodexHome(value) ?? path.join(os.homedir(), ".codex")
165
- }
166
-
167
- function expandHome(value: string): string {
168
- if (value === "~") return os.homedir()
169
- if (value.startsWith(`~${path.sep}`)) {
170
- return path.join(os.homedir(), value.slice(2))
171
- }
172
- return value
173
- }
174
-
175
162
  function resolveOutputRoot(value: unknown): string {
176
163
  if (value && String(value).trim()) {
177
164
  const expanded = expandHome(String(value).trim())
@@ -182,13 +169,28 @@ function resolveOutputRoot(value: unknown): string {
182
169
  return path.join(os.homedir(), ".config", "opencode")
183
170
  }
184
171
 
185
- function resolveTargetOutputRoot(targetName: string, outputRoot: string, codexHome: string, hasExplicitOutput: boolean): string {
172
+ function resolveTargetOutputRoot(
173
+ targetName: string,
174
+ outputRoot: string,
175
+ codexHome: string,
176
+ piHome: string,
177
+ hasExplicitOutput: boolean,
178
+ ): string {
186
179
  if (targetName === "codex") return codexHome
180
+ if (targetName === "pi") return piHome
187
181
  if (targetName === "droid") return path.join(os.homedir(), ".factory")
188
182
  if (targetName === "cursor") {
189
183
  const base = hasExplicitOutput ? outputRoot : process.cwd()
190
184
  return path.join(base, ".cursor")
191
185
  }
186
+ if (targetName === "gemini") {
187
+ const base = hasExplicitOutput ? outputRoot : process.cwd()
188
+ return path.join(base, ".gemini")
189
+ }
190
+ if (targetName === "copilot") {
191
+ const base = hasExplicitOutput ? outputRoot : process.cwd()
192
+ return path.join(base, ".github")
193
+ }
192
194
  return outputRoot
193
195
  }
194
196
 
@@ -4,9 +4,16 @@ import path from "path"
4
4
  import { loadClaudeHome } from "../parsers/claude-home"
5
5
  import { syncToOpenCode } from "../sync/opencode"
6
6
  import { syncToCodex } from "../sync/codex"
7
+ import { syncToPi } from "../sync/pi"
8
+ import { syncToDroid } from "../sync/droid"
9
+ import { syncToCopilot } from "../sync/copilot"
10
+ import { expandHome } from "../utils/resolve-home"
7
11
 
8
- function isValidTarget(value: string): value is "opencode" | "codex" {
9
- return value === "opencode" || value === "codex"
12
+ const validTargets = ["opencode", "codex", "pi", "droid", "copilot"] as const
13
+ type SyncTarget = (typeof validTargets)[number]
14
+
15
+ function isValidTarget(value: string): value is SyncTarget {
16
+ return (validTargets as readonly string[]).includes(value)
10
17
  }
11
18
 
12
19
  /** Check if any MCP servers have env vars that might contain secrets */
@@ -23,16 +30,31 @@ function hasPotentialSecrets(mcpServers: Record<string, unknown>): boolean {
23
30
  return false
24
31
  }
25
32
 
33
+ function resolveOutputRoot(target: SyncTarget): string {
34
+ switch (target) {
35
+ case "opencode":
36
+ return path.join(os.homedir(), ".config", "opencode")
37
+ case "codex":
38
+ return path.join(os.homedir(), ".codex")
39
+ case "pi":
40
+ return path.join(os.homedir(), ".pi", "agent")
41
+ case "droid":
42
+ return path.join(os.homedir(), ".factory")
43
+ case "copilot":
44
+ return path.join(process.cwd(), ".github")
45
+ }
46
+ }
47
+
26
48
  export default defineCommand({
27
49
  meta: {
28
50
  name: "sync",
29
- description: "Sync Claude Code config (~/.claude/) to OpenCode or Codex",
51
+ description: "Sync Claude Code config (~/.claude/) to OpenCode, Codex, Pi, Droid, or Copilot",
30
52
  },
31
53
  args: {
32
54
  target: {
33
55
  type: "string",
34
56
  required: true,
35
- description: "Target: opencode | codex",
57
+ description: "Target: opencode | codex | pi | droid | copilot",
36
58
  },
37
59
  claudeHome: {
38
60
  type: "string",
@@ -42,7 +64,7 @@ export default defineCommand({
42
64
  },
43
65
  async run({ args }) {
44
66
  if (!isValidTarget(args.target)) {
45
- throw new Error(`Unknown target: ${args.target}. Use 'opencode' or 'codex'.`)
67
+ throw new Error(`Unknown target: ${args.target}. Use one of: ${validTargets.join(", ")}`)
46
68
  }
47
69
 
48
70
  const claudeHome = expandHome(args.claudeHome ?? path.join(os.homedir(), ".claude"))
@@ -60,25 +82,26 @@ export default defineCommand({
60
82
  `Syncing ${config.skills.length} skills, ${Object.keys(config.mcpServers).length} MCP servers...`,
61
83
  )
62
84
 
63
- const outputRoot =
64
- args.target === "opencode"
65
- ? path.join(os.homedir(), ".config", "opencode")
66
- : path.join(os.homedir(), ".codex")
85
+ const outputRoot = resolveOutputRoot(args.target)
67
86
 
68
- if (args.target === "opencode") {
69
- await syncToOpenCode(config, outputRoot)
70
- } else {
71
- await syncToCodex(config, outputRoot)
87
+ switch (args.target) {
88
+ case "opencode":
89
+ await syncToOpenCode(config, outputRoot)
90
+ break
91
+ case "codex":
92
+ await syncToCodex(config, outputRoot)
93
+ break
94
+ case "pi":
95
+ await syncToPi(config, outputRoot)
96
+ break
97
+ case "droid":
98
+ await syncToDroid(config, outputRoot)
99
+ break
100
+ case "copilot":
101
+ await syncToCopilot(config, outputRoot)
102
+ break
72
103
  }
73
104
 
74
105
  console.log(`✓ Synced to ${args.target}: ${outputRoot}`)
75
106
  },
76
107
  })
77
-
78
- function expandHome(value: string): string {
79
- if (value === "~") return os.homedir()
80
- if (value.startsWith(`~${path.sep}`)) {
81
- return path.join(os.homedir(), value.slice(2))
82
- }
83
- return value
84
- }