feat: add first-class pi target with mcporter/subagent compatibility
This commit is contained in:
@@ -22,7 +22,7 @@ export default defineCommand({
|
||||
to: {
|
||||
type: "string",
|
||||
default: "opencode",
|
||||
description: "Target format (opencode | codex | droid | cursor)",
|
||||
description: "Target format (opencode | codex | droid | cursor | pi)",
|
||||
},
|
||||
output: {
|
||||
type: "string",
|
||||
@@ -34,6 +34,11 @@ export default defineCommand({
|
||||
alias: "codex-home",
|
||||
description: "Write Codex output to this .codex root (ex: ~/.codex)",
|
||||
},
|
||||
piHome: {
|
||||
type: "string",
|
||||
alias: "pi-home",
|
||||
description: "Write Pi output to this Pi root (ex: ~/.pi/agent or ./.pi)",
|
||||
},
|
||||
also: {
|
||||
type: "string",
|
||||
description: "Comma-separated extra targets to generate (ex: codex)",
|
||||
@@ -73,6 +78,7 @@ export default defineCommand({
|
||||
const plugin = await loadClaudePlugin(String(args.source))
|
||||
const outputRoot = resolveOutputRoot(args.output)
|
||||
const codexHome = resolveCodexRoot(args.codexHome)
|
||||
const piHome = resolvePiRoot(args.piHome)
|
||||
|
||||
const options = {
|
||||
agentMode: String(args.agentMode) === "primary" ? "primary" : "subagent",
|
||||
@@ -80,7 +86,7 @@ export default defineCommand({
|
||||
permissions: permissions as PermissionMode,
|
||||
}
|
||||
|
||||
const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome)
|
||||
const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome)
|
||||
const bundle = target.convert(plugin, options)
|
||||
if (!bundle) {
|
||||
throw new Error(`Target ${targetName} did not return a bundle.`)
|
||||
@@ -106,7 +112,7 @@ export default defineCommand({
|
||||
console.warn(`Skipping ${extra}: no output returned.`)
|
||||
continue
|
||||
}
|
||||
const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome)
|
||||
const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, piHome)
|
||||
await handler.write(extraRoot, extraBundle)
|
||||
console.log(`Converted ${plugin.manifest.name} to ${extra} at ${extraRoot}`)
|
||||
}
|
||||
@@ -137,6 +143,18 @@ function resolveCodexRoot(value: unknown): string {
|
||||
return resolveCodexHome(value) ?? path.join(os.homedir(), ".codex")
|
||||
}
|
||||
|
||||
function resolvePiHome(value: unknown): string | null {
|
||||
if (!value) return null
|
||||
const raw = String(value).trim()
|
||||
if (!raw) return null
|
||||
const expanded = expandHome(raw)
|
||||
return path.resolve(expanded)
|
||||
}
|
||||
|
||||
function resolvePiRoot(value: unknown): string {
|
||||
return resolvePiHome(value) ?? path.join(os.homedir(), ".pi", "agent")
|
||||
}
|
||||
|
||||
function expandHome(value: string): string {
|
||||
if (value === "~") return os.homedir()
|
||||
if (value.startsWith(`~${path.sep}`)) {
|
||||
@@ -153,8 +171,9 @@ function resolveOutputRoot(value: unknown): string {
|
||||
return process.cwd()
|
||||
}
|
||||
|
||||
function resolveTargetOutputRoot(targetName: string, outputRoot: string, codexHome: string): string {
|
||||
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")
|
||||
return outputRoot
|
||||
|
||||
@@ -24,7 +24,7 @@ export default defineCommand({
|
||||
to: {
|
||||
type: "string",
|
||||
default: "opencode",
|
||||
description: "Target format (opencode | codex | droid | cursor)",
|
||||
description: "Target format (opencode | codex | droid | cursor | pi)",
|
||||
},
|
||||
output: {
|
||||
type: "string",
|
||||
@@ -36,6 +36,11 @@ export default defineCommand({
|
||||
alias: "codex-home",
|
||||
description: "Write Codex output to this .codex root (ex: ~/.codex)",
|
||||
},
|
||||
piHome: {
|
||||
type: "string",
|
||||
alias: "pi-home",
|
||||
description: "Write Pi output to this Pi root (ex: ~/.pi/agent or ./.pi)",
|
||||
},
|
||||
also: {
|
||||
type: "string",
|
||||
description: "Comma-separated extra targets to generate (ex: codex)",
|
||||
@@ -77,6 +82,7 @@ export default defineCommand({
|
||||
const plugin = await loadClaudePlugin(resolvedPlugin.path)
|
||||
const outputRoot = resolveOutputRoot(args.output)
|
||||
const codexHome = resolveCodexRoot(args.codexHome)
|
||||
const piHome = resolvePiRoot(args.piHome)
|
||||
|
||||
const options = {
|
||||
agentMode: String(args.agentMode) === "primary" ? "primary" : "subagent",
|
||||
@@ -89,7 +95,7 @@ export default defineCommand({
|
||||
throw new Error(`Target ${targetName} did not return a bundle.`)
|
||||
}
|
||||
const hasExplicitOutput = Boolean(args.output && String(args.output).trim())
|
||||
const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, hasExplicitOutput)
|
||||
const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome, hasExplicitOutput)
|
||||
await target.write(primaryOutputRoot, bundle)
|
||||
console.log(`Installed ${plugin.manifest.name} to ${primaryOutputRoot}`)
|
||||
|
||||
@@ -110,7 +116,7 @@ export default defineCommand({
|
||||
console.warn(`Skipping ${extra}: no output returned.`)
|
||||
continue
|
||||
}
|
||||
const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, hasExplicitOutput)
|
||||
const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, piHome, hasExplicitOutput)
|
||||
await handler.write(extraRoot, extraBundle)
|
||||
console.log(`Installed ${plugin.manifest.name} to ${extraRoot}`)
|
||||
}
|
||||
@@ -164,6 +170,18 @@ function resolveCodexRoot(value: unknown): string {
|
||||
return resolveCodexHome(value) ?? path.join(os.homedir(), ".codex")
|
||||
}
|
||||
|
||||
function resolvePiHome(value: unknown): string | null {
|
||||
if (!value) return null
|
||||
const raw = String(value).trim()
|
||||
if (!raw) return null
|
||||
const expanded = expandHome(raw)
|
||||
return path.resolve(expanded)
|
||||
}
|
||||
|
||||
function resolvePiRoot(value: unknown): string {
|
||||
return resolvePiHome(value) ?? path.join(os.homedir(), ".pi", "agent")
|
||||
}
|
||||
|
||||
function expandHome(value: string): string {
|
||||
if (value === "~") return os.homedir()
|
||||
if (value.startsWith(`~${path.sep}`)) {
|
||||
@@ -182,8 +200,15 @@ function resolveOutputRoot(value: unknown): string {
|
||||
return path.join(os.homedir(), ".config", "opencode")
|
||||
}
|
||||
|
||||
function resolveTargetOutputRoot(targetName: string, outputRoot: string, codexHome: string, hasExplicitOutput: boolean): string {
|
||||
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()
|
||||
|
||||
@@ -4,9 +4,10 @@ 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"
|
||||
|
||||
function isValidTarget(value: string): value is "opencode" | "codex" {
|
||||
return value === "opencode" || value === "codex"
|
||||
function isValidTarget(value: string): value is "opencode" | "codex" | "pi" {
|
||||
return value === "opencode" || value === "codex" || value === "pi"
|
||||
}
|
||||
|
||||
/** Check if any MCP servers have env vars that might contain secrets */
|
||||
@@ -26,13 +27,13 @@ function hasPotentialSecrets(mcpServers: Record<string, unknown>): boolean {
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "sync",
|
||||
description: "Sync Claude Code config (~/.claude/) to OpenCode or Codex",
|
||||
description: "Sync Claude Code config (~/.claude/) to OpenCode, Codex, or Pi",
|
||||
},
|
||||
args: {
|
||||
target: {
|
||||
type: "string",
|
||||
required: true,
|
||||
description: "Target: opencode | codex",
|
||||
description: "Target: opencode | codex | pi",
|
||||
},
|
||||
claudeHome: {
|
||||
type: "string",
|
||||
@@ -42,7 +43,7 @@ export default defineCommand({
|
||||
},
|
||||
async run({ args }) {
|
||||
if (!isValidTarget(args.target)) {
|
||||
throw new Error(`Unknown target: ${args.target}. Use 'opencode' or 'codex'.`)
|
||||
throw new Error(`Unknown target: ${args.target}. Use 'opencode', 'codex', or 'pi'.`)
|
||||
}
|
||||
|
||||
const claudeHome = expandHome(args.claudeHome ?? path.join(os.homedir(), ".claude"))
|
||||
@@ -63,12 +64,16 @@ export default defineCommand({
|
||||
const outputRoot =
|
||||
args.target === "opencode"
|
||||
? path.join(os.homedir(), ".config", "opencode")
|
||||
: path.join(os.homedir(), ".codex")
|
||||
: args.target === "codex"
|
||||
? path.join(os.homedir(), ".codex")
|
||||
: path.join(os.homedir(), ".pi", "agent")
|
||||
|
||||
if (args.target === "opencode") {
|
||||
await syncToOpenCode(config, outputRoot)
|
||||
} else {
|
||||
} else if (args.target === "codex") {
|
||||
await syncToCodex(config, outputRoot)
|
||||
} else {
|
||||
await syncToPi(config, outputRoot)
|
||||
}
|
||||
|
||||
console.log(`✓ Synced to ${args.target}: ${outputRoot}`)
|
||||
|
||||
Reference in New Issue
Block a user