feat(sync): add Claude home sync parity across providers
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { pathExists } from "./files"
|
||||
import { syncTargets } from "../sync/registry"
|
||||
|
||||
export type DetectedTool = {
|
||||
name: string
|
||||
@@ -12,27 +12,18 @@ export async function detectInstalledTools(
|
||||
home: string = os.homedir(),
|
||||
cwd: string = process.cwd(),
|
||||
): Promise<DetectedTool[]> {
|
||||
const checks: Array<{ name: string; paths: string[] }> = [
|
||||
{ name: "opencode", paths: [path.join(home, ".config", "opencode"), path.join(cwd, ".opencode")] },
|
||||
{ name: "codex", paths: [path.join(home, ".codex")] },
|
||||
{ name: "droid", paths: [path.join(home, ".factory")] },
|
||||
{ name: "cursor", paths: [path.join(cwd, ".cursor"), path.join(home, ".cursor")] },
|
||||
{ name: "pi", paths: [path.join(home, ".pi")] },
|
||||
{ name: "gemini", paths: [path.join(cwd, ".gemini"), path.join(home, ".gemini")] },
|
||||
]
|
||||
|
||||
const results: DetectedTool[] = []
|
||||
for (const check of checks) {
|
||||
for (const target of syncTargets) {
|
||||
let detected = false
|
||||
let reason = "not found"
|
||||
for (const p of check.paths) {
|
||||
for (const p of target.detectPaths(home, cwd)) {
|
||||
if (await pathExists(p)) {
|
||||
detected = true
|
||||
reason = `found ${p}`
|
||||
break
|
||||
}
|
||||
}
|
||||
results.push({ name: check.name, detected, reason })
|
||||
results.push({ name: target.name, detected, reason })
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -41,6 +41,12 @@ export async function writeText(filePath: string, content: string): Promise<void
|
||||
await fs.writeFile(filePath, content, "utf8")
|
||||
}
|
||||
|
||||
export async function writeTextSecure(filePath: string, content: string): Promise<void> {
|
||||
await ensureDir(path.dirname(filePath))
|
||||
await fs.writeFile(filePath, content, { encoding: "utf8", mode: 0o600 })
|
||||
await fs.chmod(filePath, 0o600)
|
||||
}
|
||||
|
||||
export async function writeJson(filePath: string, data: unknown): Promise<void> {
|
||||
const content = JSON.stringify(data, null, 2)
|
||||
await writeText(filePath, content + "\n")
|
||||
@@ -51,6 +57,7 @@ export async function writeJsonSecure(filePath: string, data: unknown): Promise<
|
||||
const content = JSON.stringify(data, null, 2)
|
||||
await ensureDir(path.dirname(filePath))
|
||||
await fs.writeFile(filePath, content + "\n", { encoding: "utf8", mode: 0o600 })
|
||||
await fs.chmod(filePath, 0o600)
|
||||
}
|
||||
|
||||
export async function walkFiles(root: string): Promise<string[]> {
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "fs/promises"
|
||||
|
||||
/**
|
||||
* Create a symlink, safely replacing any existing symlink at target.
|
||||
* Only removes existing symlinks - refuses to delete real directories.
|
||||
* Only removes existing symlinks - skips real directories with a warning.
|
||||
*/
|
||||
export async function forceSymlink(source: string, target: string): Promise<void> {
|
||||
try {
|
||||
@@ -11,11 +11,9 @@ export async function forceSymlink(source: string, target: string): Promise<void
|
||||
// Safe to remove existing symlink
|
||||
await fs.unlink(target)
|
||||
} else if (stat.isDirectory()) {
|
||||
// Refuse to delete real directories
|
||||
throw new Error(
|
||||
`Cannot create symlink at ${target}: a real directory exists there. ` +
|
||||
`Remove it manually if you want to replace it with a symlink.`
|
||||
)
|
||||
// Skip real directories rather than deleting them
|
||||
console.warn(`Skipping ${target}: a real directory exists there (remove it manually to replace with a symlink).`)
|
||||
return
|
||||
} else {
|
||||
// Regular file - remove it
|
||||
await fs.unlink(target)
|
||||
|
||||
Reference in New Issue
Block a user