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:
@@ -46,6 +46,13 @@ export async function writeJson(filePath: string, data: unknown): Promise<void>
|
||||
await writeText(filePath, content + "\n")
|
||||
}
|
||||
|
||||
/** Write JSON with restrictive permissions (0o600) for files containing secrets */
|
||||
export async function writeJsonSecure(filePath: string, data: unknown): Promise<void> {
|
||||
const content = JSON.stringify(data, null, 2)
|
||||
await ensureDir(path.dirname(filePath))
|
||||
await fs.writeFile(filePath, content + "\n", { encoding: "utf8", mode: 0o600 })
|
||||
}
|
||||
|
||||
export async function walkFiles(root: string): Promise<string[]> {
|
||||
const entries = await fs.readdir(root, { withFileTypes: true })
|
||||
const results: string[] = []
|
||||
|
||||
@@ -58,7 +58,7 @@ function formatYamlValue(value: unknown): string {
|
||||
if (raw.includes("\n")) {
|
||||
return `|\n${raw.split("\n").map((line) => ` ${line}`).join("\n")}`
|
||||
}
|
||||
if (raw.includes(":") || raw.startsWith("[") || raw.startsWith("{")) {
|
||||
if (raw.includes(":") || raw.startsWith("[") || raw.startsWith("{") || raw === "*") {
|
||||
return JSON.stringify(raw)
|
||||
}
|
||||
return raw
|
||||
|
||||
50
src/utils/resolve-output.ts
Normal file
50
src/utils/resolve-output.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import type { TargetScope } from "../targets"
|
||||
|
||||
export function resolveTargetOutputRoot(options: {
|
||||
targetName: string
|
||||
outputRoot: string
|
||||
codexHome: string
|
||||
piHome: string
|
||||
openclawHome?: string
|
||||
qwenHome?: string
|
||||
pluginName?: string
|
||||
hasExplicitOutput: boolean
|
||||
scope?: TargetScope
|
||||
}): string {
|
||||
const { targetName, outputRoot, codexHome, piHome, openclawHome, qwenHome, pluginName, hasExplicitOutput, scope } = options
|
||||
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")
|
||||
}
|
||||
if (targetName === "copilot") {
|
||||
const base = hasExplicitOutput ? outputRoot : process.cwd()
|
||||
return path.join(base, ".github")
|
||||
}
|
||||
if (targetName === "kiro") {
|
||||
const base = hasExplicitOutput ? outputRoot : process.cwd()
|
||||
return path.join(base, ".kiro")
|
||||
}
|
||||
if (targetName === "windsurf") {
|
||||
if (hasExplicitOutput) return outputRoot
|
||||
if (scope === "global") return path.join(os.homedir(), ".codeium", "windsurf")
|
||||
return path.join(process.cwd(), ".windsurf")
|
||||
}
|
||||
if (targetName === "openclaw") {
|
||||
const home = openclawHome ?? path.join(os.homedir(), ".openclaw", "extensions")
|
||||
return path.join(home, pluginName ?? "plugin")
|
||||
}
|
||||
if (targetName === "qwen") {
|
||||
const home = qwenHome ?? path.join(os.homedir(), ".qwen", "extensions")
|
||||
return path.join(home, pluginName ?? "plugin")
|
||||
}
|
||||
return outputRoot
|
||||
}
|
||||
24
src/utils/secrets.ts
Normal file
24
src/utils/secrets.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export const SENSITIVE_PATTERN = /key|token|secret|password|credential|api_key/i
|
||||
|
||||
/** Check if any MCP servers have env vars that might contain secrets */
|
||||
export function hasPotentialSecrets(
|
||||
servers: Record<string, { env?: Record<string, string> }>,
|
||||
): boolean {
|
||||
for (const server of Object.values(servers)) {
|
||||
if (server.env) {
|
||||
for (const key of Object.keys(server.env)) {
|
||||
if (SENSITIVE_PATTERN.test(key)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** Return names of MCP servers whose env vars may contain secrets */
|
||||
export function findServersWithPotentialSecrets(
|
||||
servers: Record<string, { env?: Record<string, string> }>,
|
||||
): string[] {
|
||||
return Object.entries(servers)
|
||||
.filter(([, s]) => s.env && Object.keys(s.env).some((k) => SENSITIVE_PATTERN.test(k)))
|
||||
.map(([name]) => name)
|
||||
}
|
||||
Reference in New Issue
Block a user