feat(sync): add Claude home sync parity across providers
This commit is contained in:
@@ -1,76 +1,34 @@
|
||||
import { defineCommand } from "citty"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { loadClaudeHome } from "../parsers/claude-home"
|
||||
import { syncToOpenCode } from "../sync/opencode"
|
||||
import { syncToCodex } from "../sync/codex"
|
||||
import { syncToPi } from "../sync/pi"
|
||||
import { syncToDroid } from "../sync/droid"
|
||||
import { syncToCopilot } from "../sync/copilot"
|
||||
import { syncToGemini } from "../sync/gemini"
|
||||
import {
|
||||
getDefaultSyncRegistryContext,
|
||||
getSyncTarget,
|
||||
isSyncTargetName,
|
||||
syncTargetNames,
|
||||
type SyncTargetName,
|
||||
} from "../sync/registry"
|
||||
import { expandHome } from "../utils/resolve-home"
|
||||
import { hasPotentialSecrets } from "../utils/secrets"
|
||||
import { detectInstalledTools } from "../utils/detect-tools"
|
||||
|
||||
const validTargets = ["opencode", "codex", "pi", "droid", "copilot", "gemini", "all"] as const
|
||||
type SyncTarget = (typeof validTargets)[number]
|
||||
const validTargets = [...syncTargetNames, "all"] as const
|
||||
type SyncTarget = SyncTargetName | "all"
|
||||
|
||||
function isValidTarget(value: string): value is SyncTarget {
|
||||
return (validTargets as readonly string[]).includes(value)
|
||||
}
|
||||
|
||||
function resolveOutputRoot(target: string): string {
|
||||
switch (target) {
|
||||
case "opencode":
|
||||
return path.join(os.homedir(), ".config", "opencode")
|
||||
case "codex":
|
||||
return path.join(os.homedir(), ".codex")
|
||||
case "pi":
|
||||
return path.join(os.homedir(), ".pi", "agent")
|
||||
case "droid":
|
||||
return path.join(os.homedir(), ".factory")
|
||||
case "copilot":
|
||||
return path.join(process.cwd(), ".github")
|
||||
case "gemini":
|
||||
return path.join(process.cwd(), ".gemini")
|
||||
default:
|
||||
throw new Error(`No output root for target: ${target}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function syncTarget(target: string, config: Awaited<ReturnType<typeof loadClaudeHome>>, outputRoot: string): Promise<void> {
|
||||
switch (target) {
|
||||
case "opencode":
|
||||
await syncToOpenCode(config, outputRoot)
|
||||
break
|
||||
case "codex":
|
||||
await syncToCodex(config, outputRoot)
|
||||
break
|
||||
case "pi":
|
||||
await syncToPi(config, outputRoot)
|
||||
break
|
||||
case "droid":
|
||||
await syncToDroid(config, outputRoot)
|
||||
break
|
||||
case "copilot":
|
||||
await syncToCopilot(config, outputRoot)
|
||||
break
|
||||
case "gemini":
|
||||
await syncToGemini(config, outputRoot)
|
||||
break
|
||||
}
|
||||
return value === "all" || isSyncTargetName(value)
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "sync",
|
||||
description: "Sync Claude Code config (~/.claude/) to OpenCode, Codex, Pi, Droid, Copilot, or Gemini",
|
||||
description: "Sync Claude Code config (~/.claude/) to supported provider configs and skills",
|
||||
},
|
||||
args: {
|
||||
target: {
|
||||
type: "string",
|
||||
default: "all",
|
||||
description: "Target: opencode | codex | pi | droid | copilot | gemini | all (default: all)",
|
||||
description: `Target: ${syncTargetNames.join(" | ")} | all (default: all)`,
|
||||
},
|
||||
claudeHome: {
|
||||
type: "string",
|
||||
@@ -83,7 +41,8 @@ export default defineCommand({
|
||||
throw new Error(`Unknown target: ${args.target}. Use one of: ${validTargets.join(", ")}`)
|
||||
}
|
||||
|
||||
const claudeHome = expandHome(args.claudeHome ?? path.join(os.homedir(), ".claude"))
|
||||
const { home, cwd } = getDefaultSyncRegistryContext()
|
||||
const claudeHome = expandHome(args.claudeHome ?? path.join(home, ".claude"))
|
||||
const config = await loadClaudeHome(claudeHome)
|
||||
|
||||
// Warn about potential secrets in MCP env vars
|
||||
@@ -109,19 +68,21 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
for (const name of activeTargets) {
|
||||
const outputRoot = resolveOutputRoot(name)
|
||||
await syncTarget(name, config, outputRoot)
|
||||
const target = getSyncTarget(name as SyncTargetName)
|
||||
const outputRoot = target.resolveOutputRoot(home, cwd)
|
||||
await target.sync(config, outputRoot)
|
||||
console.log(`✓ Synced to ${name}: ${outputRoot}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Syncing ${config.skills.length} skills, ${Object.keys(config.mcpServers).length} MCP servers...`,
|
||||
`Syncing ${config.skills.length} skills, ${config.commands?.length ?? 0} commands, ${Object.keys(config.mcpServers).length} MCP servers...`,
|
||||
)
|
||||
|
||||
const outputRoot = resolveOutputRoot(args.target)
|
||||
await syncTarget(args.target, config, outputRoot)
|
||||
const target = getSyncTarget(args.target as SyncTargetName)
|
||||
const outputRoot = target.resolveOutputRoot(home, cwd)
|
||||
await target.sync(config, outputRoot)
|
||||
console.log(`✓ Synced to ${args.target}: ${outputRoot}`)
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user