feat(sync): add Claude home sync parity across providers
This commit is contained in:
@@ -1,22 +1,26 @@
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import fs from "fs/promises"
|
||||
import type { ClaudeSkill, ClaudeMcpServer } from "../types/claude"
|
||||
import { parseFrontmatter } from "../utils/frontmatter"
|
||||
import { walkFiles } from "../utils/files"
|
||||
import type { ClaudeCommand, ClaudeSkill, ClaudeMcpServer } from "../types/claude"
|
||||
|
||||
export interface ClaudeHomeConfig {
|
||||
skills: ClaudeSkill[]
|
||||
commands?: ClaudeCommand[]
|
||||
mcpServers: Record<string, ClaudeMcpServer>
|
||||
}
|
||||
|
||||
export async function loadClaudeHome(claudeHome?: string): Promise<ClaudeHomeConfig> {
|
||||
const home = claudeHome ?? path.join(os.homedir(), ".claude")
|
||||
|
||||
const [skills, mcpServers] = await Promise.all([
|
||||
const [skills, commands, mcpServers] = await Promise.all([
|
||||
loadPersonalSkills(path.join(home, "skills")),
|
||||
loadPersonalCommands(path.join(home, "commands")),
|
||||
loadSettingsMcp(path.join(home, "settings.json")),
|
||||
])
|
||||
|
||||
return { skills, mcpServers }
|
||||
return { skills, commands, mcpServers }
|
||||
}
|
||||
|
||||
async function loadPersonalSkills(skillsDir: string): Promise<ClaudeSkill[]> {
|
||||
@@ -63,3 +67,51 @@ async function loadSettingsMcp(
|
||||
return {} // File doesn't exist or invalid JSON
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPersonalCommands(commandsDir: string): Promise<ClaudeCommand[]> {
|
||||
try {
|
||||
const files = (await walkFiles(commandsDir))
|
||||
.filter((file) => file.endsWith(".md"))
|
||||
.sort()
|
||||
|
||||
const commands: ClaudeCommand[] = []
|
||||
for (const file of files) {
|
||||
const raw = await fs.readFile(file, "utf8")
|
||||
const { data, body } = parseFrontmatter(raw)
|
||||
commands.push({
|
||||
name: typeof data.name === "string" ? data.name : deriveCommandName(commandsDir, file),
|
||||
description: data.description as string | undefined,
|
||||
argumentHint: data["argument-hint"] as string | undefined,
|
||||
model: data.model as string | undefined,
|
||||
allowedTools: parseAllowedTools(data["allowed-tools"]),
|
||||
disableModelInvocation: data["disable-model-invocation"] === true ? true : undefined,
|
||||
body: body.trim(),
|
||||
sourcePath: file,
|
||||
})
|
||||
}
|
||||
|
||||
return commands
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function deriveCommandName(commandsDir: string, filePath: string): string {
|
||||
const relative = path.relative(commandsDir, filePath)
|
||||
const withoutExt = relative.replace(/\.md$/i, "")
|
||||
return withoutExt.split(path.sep).join(":")
|
||||
}
|
||||
|
||||
function parseAllowedTools(value: unknown): string[] | undefined {
|
||||
if (!value) return undefined
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => String(item))
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value
|
||||
.split(/,/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user