chore: Resolve conflicts with main, update to v0.12.0
- sync.ts: add gemini + all targets, keep copilot, remove cursor (native), use shared hasPotentialSecrets - install.ts + convert.ts: import both detectInstalledTools and resolveTargetOutputRoot; update --to all block to use new object API; fix resolvedScope ordering (was referencing target before definition) - CHANGELOG.md: add v0.12.0 entry (auto-detect + Gemini sync) - README.md: merge all install targets, collapsible output format table, sync defaults to --target all - package.json: bump to 0.12.0 - sync --target now defaults to "all" when omitted 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,10 +2,11 @@ import { defineCommand } from "citty"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { loadClaudePlugin } from "../parsers/claude"
|
||||
import { targets } from "../targets"
|
||||
import { targets, validateScope } from "../targets"
|
||||
import type { PermissionMode } from "../converters/claude-to-opencode"
|
||||
import { ensureCodexAgentsFile } from "../utils/codex-agents"
|
||||
import { expandHome, resolveTargetHome } from "../utils/resolve-home"
|
||||
import { resolveTargetOutputRoot } from "../utils/resolve-output"
|
||||
import { detectInstalledTools } from "../utils/detect-tools"
|
||||
|
||||
const permissionModes: PermissionMode[] = ["none", "broad", "from-commands"]
|
||||
@@ -24,7 +25,7 @@ export default defineCommand({
|
||||
to: {
|
||||
type: "string",
|
||||
default: "opencode",
|
||||
description: "Target format (opencode | codex | droid | cursor | pi | gemini | all)",
|
||||
description: "Target format (opencode | codex | droid | cursor | pi | copilot | gemini | kiro | windsurf | openclaw | qwen | all)",
|
||||
},
|
||||
output: {
|
||||
type: "string",
|
||||
@@ -41,6 +42,20 @@ export default defineCommand({
|
||||
alias: "pi-home",
|
||||
description: "Write Pi output to this Pi root (ex: ~/.pi/agent or ./.pi)",
|
||||
},
|
||||
openclawHome: {
|
||||
type: "string",
|
||||
alias: "openclaw-home",
|
||||
description: "Write OpenClaw output to this extensions root (ex: ~/.openclaw/extensions)",
|
||||
},
|
||||
qwenHome: {
|
||||
type: "string",
|
||||
alias: "qwen-home",
|
||||
description: "Write Qwen output to this Qwen extensions root (ex: ~/.qwen/extensions)",
|
||||
},
|
||||
scope: {
|
||||
type: "string",
|
||||
description: "Scope level: global | workspace (default varies by target)",
|
||||
},
|
||||
also: {
|
||||
type: "string",
|
||||
description: "Comma-separated extra targets to generate (ex: codex)",
|
||||
@@ -71,8 +86,11 @@ export default defineCommand({
|
||||
|
||||
const plugin = await loadClaudePlugin(String(args.source))
|
||||
const outputRoot = resolveOutputRoot(args.output)
|
||||
const hasExplicitOutput = Boolean(args.output && String(args.output).trim())
|
||||
const codexHome = resolveTargetHome(args.codexHome, path.join(os.homedir(), ".codex"))
|
||||
const piHome = resolveTargetHome(args.piHome, path.join(os.homedir(), ".pi", "agent"))
|
||||
const openclawHome = resolveTargetHome(args.openclawHome, path.join(os.homedir(), ".openclaw", "extensions"))
|
||||
const qwenHome = resolveTargetHome(args.qwenHome, path.join(os.homedir(), ".qwen", "extensions"))
|
||||
|
||||
const options = {
|
||||
agentMode: String(args.agentMode) === "primary" ? "primary" : "subagent",
|
||||
@@ -105,7 +123,16 @@ export default defineCommand({
|
||||
console.warn(`Skipping ${tool.name}: no output returned.`)
|
||||
continue
|
||||
}
|
||||
const root = resolveTargetOutputRoot(tool.name, outputRoot, codexHome, piHome)
|
||||
const root = resolveTargetOutputRoot({
|
||||
targetName: tool.name,
|
||||
outputRoot,
|
||||
codexHome,
|
||||
piHome,
|
||||
openclawHome,
|
||||
qwenHome,
|
||||
pluginName: plugin.manifest.name,
|
||||
hasExplicitOutput,
|
||||
})
|
||||
await handler.write(root, bundle)
|
||||
console.log(`Converted ${plugin.manifest.name} to ${tool.name} at ${root}`)
|
||||
}
|
||||
@@ -125,13 +152,25 @@ export default defineCommand({
|
||||
throw new Error(`Target ${targetName} is registered but not implemented yet.`)
|
||||
}
|
||||
|
||||
const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome)
|
||||
const resolvedScope = validateScope(targetName, target, args.scope ? String(args.scope) : undefined)
|
||||
|
||||
const primaryOutputRoot = resolveTargetOutputRoot({
|
||||
targetName,
|
||||
outputRoot,
|
||||
codexHome,
|
||||
piHome,
|
||||
openclawHome,
|
||||
qwenHome,
|
||||
pluginName: plugin.manifest.name,
|
||||
hasExplicitOutput,
|
||||
scope: resolvedScope,
|
||||
})
|
||||
const bundle = target.convert(plugin, options)
|
||||
if (!bundle) {
|
||||
throw new Error(`Target ${targetName} did not return a bundle.`)
|
||||
}
|
||||
|
||||
await target.write(primaryOutputRoot, bundle)
|
||||
await target.write(primaryOutputRoot, bundle, resolvedScope)
|
||||
console.log(`Converted ${plugin.manifest.name} to ${targetName} at ${primaryOutputRoot}`)
|
||||
|
||||
const extraTargets = parseExtraTargets(args.also)
|
||||
@@ -151,8 +190,18 @@ export default defineCommand({
|
||||
console.warn(`Skipping ${extra}: no output returned.`)
|
||||
continue
|
||||
}
|
||||
const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, piHome)
|
||||
await handler.write(extraRoot, extraBundle)
|
||||
const extraRoot = resolveTargetOutputRoot({
|
||||
targetName: extra,
|
||||
outputRoot: path.join(outputRoot, extra),
|
||||
codexHome,
|
||||
piHome,
|
||||
openclawHome,
|
||||
qwenHome,
|
||||
pluginName: plugin.manifest.name,
|
||||
hasExplicitOutput,
|
||||
scope: handler.defaultScope,
|
||||
})
|
||||
await handler.write(extraRoot, extraBundle, handler.defaultScope)
|
||||
console.log(`Converted ${plugin.manifest.name} to ${extra} at ${extraRoot}`)
|
||||
}
|
||||
|
||||
@@ -177,12 +226,3 @@ function resolveOutputRoot(value: unknown): string {
|
||||
}
|
||||
return process.cwd()
|
||||
}
|
||||
|
||||
function resolveTargetOutputRoot(targetName: string, outputRoot: string, codexHome: string, piHome: string): string {
|
||||
if (targetName === "codex") return codexHome
|
||||
if (targetName === "pi") return piHome
|
||||
if (targetName === "droid") return path.join(os.homedir(), ".factory")
|
||||
if (targetName === "cursor") return path.join(outputRoot, ".cursor")
|
||||
if (targetName === "gemini") return path.join(outputRoot, ".gemini")
|
||||
return outputRoot
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ import { promises as fs } from "fs"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { loadClaudePlugin } from "../parsers/claude"
|
||||
import { targets } from "../targets"
|
||||
import { targets, validateScope } from "../targets"
|
||||
import { pathExists } from "../utils/files"
|
||||
import type { PermissionMode } from "../converters/claude-to-opencode"
|
||||
import { ensureCodexAgentsFile } from "../utils/codex-agents"
|
||||
import { expandHome, resolveTargetHome } from "../utils/resolve-home"
|
||||
import { resolveTargetOutputRoot } from "../utils/resolve-output"
|
||||
import { detectInstalledTools } from "../utils/detect-tools"
|
||||
|
||||
const permissionModes: PermissionMode[] = ["none", "broad", "from-commands"]
|
||||
@@ -26,7 +27,7 @@ export default defineCommand({
|
||||
to: {
|
||||
type: "string",
|
||||
default: "opencode",
|
||||
description: "Target format (opencode | codex | droid | cursor | pi | gemini | all)",
|
||||
description: "Target format (opencode | codex | droid | cursor | pi | copilot | gemini | kiro | windsurf | openclaw | qwen | all)",
|
||||
},
|
||||
output: {
|
||||
type: "string",
|
||||
@@ -43,14 +44,28 @@ export default defineCommand({
|
||||
alias: "pi-home",
|
||||
description: "Write Pi output to this Pi root (ex: ~/.pi/agent or ./.pi)",
|
||||
},
|
||||
openclawHome: {
|
||||
type: "string",
|
||||
alias: "openclaw-home",
|
||||
description: "Write OpenClaw output to this extensions root (ex: ~/.openclaw/extensions)",
|
||||
},
|
||||
qwenHome: {
|
||||
type: "string",
|
||||
alias: "qwen-home",
|
||||
description: "Write Qwen output to this Qwen extensions root (ex: ~/.qwen/extensions)",
|
||||
},
|
||||
scope: {
|
||||
type: "string",
|
||||
description: "Scope level: global | workspace (default varies by target)",
|
||||
},
|
||||
also: {
|
||||
type: "string",
|
||||
description: "Comma-separated extra targets to generate (ex: codex)",
|
||||
},
|
||||
permissions: {
|
||||
type: "string",
|
||||
default: "broad",
|
||||
description: "Permission mapping: none | broad | from-commands",
|
||||
default: "none", // Default is "none" -- writing global permissions to opencode.json pollutes user config. See ADR-003.
|
||||
description: "Permission mapping written to opencode.json: none (default) | broad | from-command",
|
||||
},
|
||||
agentMode: {
|
||||
type: "string",
|
||||
@@ -79,6 +94,8 @@ export default defineCommand({
|
||||
const codexHome = resolveTargetHome(args.codexHome, path.join(os.homedir(), ".codex"))
|
||||
const piHome = resolveTargetHome(args.piHome, path.join(os.homedir(), ".pi", "agent"))
|
||||
const hasExplicitOutput = Boolean(args.output && String(args.output).trim())
|
||||
const openclawHome = resolveTargetHome(args.openclawHome, path.join(os.homedir(), ".openclaw", "extensions"))
|
||||
const qwenHome = resolveTargetHome(args.qwenHome, path.join(os.homedir(), ".qwen", "extensions"))
|
||||
|
||||
const options = {
|
||||
agentMode: String(args.agentMode) === "primary" ? "primary" : "subagent",
|
||||
@@ -111,7 +128,16 @@ export default defineCommand({
|
||||
console.warn(`Skipping ${tool.name}: no output returned.`)
|
||||
continue
|
||||
}
|
||||
const root = resolveTargetOutputRoot(tool.name, outputRoot, codexHome, piHome, hasExplicitOutput)
|
||||
const root = resolveTargetOutputRoot({
|
||||
targetName: tool.name,
|
||||
outputRoot,
|
||||
codexHome,
|
||||
piHome,
|
||||
openclawHome,
|
||||
qwenHome,
|
||||
pluginName: plugin.manifest.name,
|
||||
hasExplicitOutput,
|
||||
})
|
||||
await handler.write(root, bundle)
|
||||
console.log(`Installed ${plugin.manifest.name} to ${tool.name} at ${root}`)
|
||||
}
|
||||
@@ -130,12 +156,24 @@ export default defineCommand({
|
||||
throw new Error(`Target ${targetName} is registered but not implemented yet.`)
|
||||
}
|
||||
|
||||
const resolvedScope = validateScope(targetName, target, args.scope ? String(args.scope) : undefined)
|
||||
|
||||
const bundle = target.convert(plugin, options)
|
||||
if (!bundle) {
|
||||
throw new Error(`Target ${targetName} did not return a bundle.`)
|
||||
}
|
||||
const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome, hasExplicitOutput)
|
||||
await target.write(primaryOutputRoot, bundle)
|
||||
const primaryOutputRoot = resolveTargetOutputRoot({
|
||||
targetName,
|
||||
outputRoot,
|
||||
codexHome,
|
||||
piHome,
|
||||
openclawHome,
|
||||
qwenHome,
|
||||
pluginName: plugin.manifest.name,
|
||||
hasExplicitOutput,
|
||||
scope: resolvedScope,
|
||||
})
|
||||
await target.write(primaryOutputRoot, bundle, resolvedScope)
|
||||
console.log(`Installed ${plugin.manifest.name} to ${primaryOutputRoot}`)
|
||||
|
||||
const extraTargets = parseExtraTargets(args.also)
|
||||
@@ -155,8 +193,18 @@ export default defineCommand({
|
||||
console.warn(`Skipping ${extra}: no output returned.`)
|
||||
continue
|
||||
}
|
||||
const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, piHome, hasExplicitOutput)
|
||||
await handler.write(extraRoot, extraBundle)
|
||||
const extraRoot = resolveTargetOutputRoot({
|
||||
targetName: extra,
|
||||
outputRoot: path.join(outputRoot, extra),
|
||||
codexHome,
|
||||
piHome,
|
||||
openclawHome,
|
||||
qwenHome,
|
||||
pluginName: plugin.manifest.name,
|
||||
hasExplicitOutput,
|
||||
scope: handler.defaultScope,
|
||||
})
|
||||
await handler.write(extraRoot, extraBundle, handler.defaultScope)
|
||||
console.log(`Installed ${plugin.manifest.name} to ${extraRoot}`)
|
||||
}
|
||||
|
||||
@@ -207,27 +255,6 @@ function resolveOutputRoot(value: unknown): string {
|
||||
return path.join(os.homedir(), ".config", "opencode")
|
||||
}
|
||||
|
||||
function resolveTargetOutputRoot(
|
||||
targetName: string,
|
||||
outputRoot: string,
|
||||
codexHome: string,
|
||||
piHome: string,
|
||||
hasExplicitOutput: boolean,
|
||||
): string {
|
||||
if (targetName === "codex") return codexHome
|
||||
if (targetName === "pi") return piHome
|
||||
if (targetName === "droid") return path.join(os.homedir(), ".factory")
|
||||
if (targetName === "cursor") {
|
||||
const base = hasExplicitOutput ? outputRoot : process.cwd()
|
||||
return path.join(base, ".cursor")
|
||||
}
|
||||
if (targetName === "gemini") {
|
||||
const base = hasExplicitOutput ? outputRoot : process.cwd()
|
||||
return path.join(base, ".gemini")
|
||||
}
|
||||
return outputRoot
|
||||
}
|
||||
|
||||
async function resolveGitHubPluginPath(pluginName: string): Promise<ResolvedPluginPath> {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compound-plugin-"))
|
||||
const source = resolveGitHubSource()
|
||||
|
||||
@@ -6,32 +6,19 @@ import { syncToOpenCode } from "../sync/opencode"
|
||||
import { syncToCodex } from "../sync/codex"
|
||||
import { syncToPi } from "../sync/pi"
|
||||
import { syncToDroid } from "../sync/droid"
|
||||
import { syncToCursor } from "../sync/cursor"
|
||||
import { syncToCopilot } from "../sync/copilot"
|
||||
import { syncToGemini } from "../sync/gemini"
|
||||
import { expandHome } from "../utils/resolve-home"
|
||||
import { hasPotentialSecrets } from "../utils/secrets"
|
||||
import { detectInstalledTools } from "../utils/detect-tools"
|
||||
|
||||
const validTargets = ["opencode", "codex", "pi", "droid", "cursor", "gemini", "all"] as const
|
||||
const validTargets = ["opencode", "codex", "pi", "droid", "copilot", "gemini", "all"] as const
|
||||
type SyncTarget = (typeof validTargets)[number]
|
||||
|
||||
function isValidTarget(value: string): value is SyncTarget {
|
||||
return (validTargets as readonly string[]).includes(value)
|
||||
}
|
||||
|
||||
/** Check if any MCP servers have env vars that might contain secrets */
|
||||
function hasPotentialSecrets(mcpServers: Record<string, unknown>): boolean {
|
||||
const sensitivePatterns = /key|token|secret|password|credential|api_key/i
|
||||
for (const server of Object.values(mcpServers)) {
|
||||
const env = (server as { env?: Record<string, string> }).env
|
||||
if (env) {
|
||||
for (const key of Object.keys(env)) {
|
||||
if (sensitivePatterns.test(key)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function resolveOutputRoot(target: string): string {
|
||||
switch (target) {
|
||||
case "opencode":
|
||||
@@ -42,8 +29,8 @@ function resolveOutputRoot(target: string): string {
|
||||
return path.join(os.homedir(), ".pi", "agent")
|
||||
case "droid":
|
||||
return path.join(os.homedir(), ".factory")
|
||||
case "cursor":
|
||||
return path.join(process.cwd(), ".cursor")
|
||||
case "copilot":
|
||||
return path.join(process.cwd(), ".github")
|
||||
case "gemini":
|
||||
return path.join(process.cwd(), ".gemini")
|
||||
default:
|
||||
@@ -65,8 +52,8 @@ async function syncTarget(target: string, config: Awaited<ReturnType<typeof load
|
||||
case "droid":
|
||||
await syncToDroid(config, outputRoot)
|
||||
break
|
||||
case "cursor":
|
||||
await syncToCursor(config, outputRoot)
|
||||
case "copilot":
|
||||
await syncToCopilot(config, outputRoot)
|
||||
break
|
||||
case "gemini":
|
||||
await syncToGemini(config, outputRoot)
|
||||
@@ -77,13 +64,13 @@ async function syncTarget(target: string, config: Awaited<ReturnType<typeof load
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "sync",
|
||||
description: "Sync Claude Code config (~/.claude/) to OpenCode, Codex, Pi, Droid, Cursor, or Gemini",
|
||||
description: "Sync Claude Code config (~/.claude/) to OpenCode, Codex, Pi, Droid, Copilot, or Gemini",
|
||||
},
|
||||
args: {
|
||||
target: {
|
||||
type: "string",
|
||||
required: true,
|
||||
description: "Target: opencode | codex | pi | droid | cursor | gemini | all",
|
||||
default: "all",
|
||||
description: "Target: opencode | codex | pi | droid | copilot | gemini | all (default: all)",
|
||||
},
|
||||
claudeHome: {
|
||||
type: "string",
|
||||
|
||||
Reference in New Issue
Block a user