Documentation
¶
Overview ¶
Package claude implements the Claude Code adapter for agentsync.
Index ¶
- func EncodeFrontmatter(fm map[string]any, body string) ([]byte, error)
- func ExtraNativeKeys(raw map[string]any, modeled ...string) map[string]any
- func MergeExtra(spec, extra map[string]any)
- func MergeKeys(existing, ours map[string]any, ownedPointers []string) (map[string]any, []string, []string)
- func ParseFrontmatter(data []byte) (map[string]any, string, error)
- func ParseFrontmatterWithReport(data []byte) (fm map[string]any, body string, lenient bool, err error)
- func SkillFileOps(skills []source.Skill, skillsDir string) ([]adapter.FileOp, error)
- type Adapter
- func (a *Adapter) Apply(ops []adapter.FileOp, w adapter.DestWriter) error
- func (a *Adapter) Capabilities() adapter.Capability
- func (a *Adapter) Detect() (bool, error)
- func (a *Adapter) Ingest(scope adapter.Scope, project string) (source.Canonical, error)
- func (a *Adapter) IngestPlugins(scope adapter.Scope, project string) ([]adapter.NativeMarketplace, []adapter.NativePlugin, error)
- func (a *Adapter) KeyMergeStrategy() string
- func (a *Adapter) Name() string
- func (a *Adapter) Render(r secrets.Resolved, scope adapter.Scope, project string) ([]adapter.FileOp, []adapter.Skip, error)
- func (a *Adapter) SetStderr(w io.Writer)
- type Options
- type Paths
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EncodeFrontmatter ¶
EncodeFrontmatter writes "---\n<yaml>\n---\n<body>" with the keys in fm. fm is empty: returns just the body.
func ExtraNativeKeys ¶
ExtraNativeKeys returns the entries of a decoded native config object whose keys are NOT in modeled — the fields agentsync does not represent in its canonical model. Adapters capture these verbatim into source.*Spec.Extra so the dest→source round-trip (import/reconcile) is not lossy for native fields agentsync doesn't understand (e.g. an MCP server's timeout/disabled/cwd). Returns nil when there are none, so an empty Extra omits the canonical [server.extra] table entirely.
func MergeExtra ¶
MergeExtra projects passthrough native fields back into a rendered destination object. A modeled key the renderer already set always wins — Extra never clobbers a field agentsync owns — so a value that was promoted into the model can't be shadowed by a stale Extra copy.
func MergeKeys ¶
func MergeKeys(existing, ours map[string]any, ownedPointers []string) (map[string]any, []string, []string)
MergeKeys is preserved as a re-export for backward compat with claude internal callers; new callers should import internal/jsonkeys directly.
func ParseFrontmatter ¶
ParseFrontmatter extracts the YAML frontmatter and the markdown body. If the input doesn't begin with "---\n", returns an empty map and the entire input as body. Returns an error on any YAML decode failure; callers that want to accept Claude Code's looser "key: value-with-colons" frontmatter should use ParseFrontmatterWithReport instead.
func ParseFrontmatterWithReport ¶
func ParseFrontmatterWithReport(data []byte) (fm map[string]any, body string, lenient bool, err error)
ParseFrontmatterWithReport is the lenient-fallback variant. It first tries strict YAML; on decode failure it falls back to a line-oriented "key: rest- of-line" parser that accepts bare colon-space inside values. lenient is true iff the strict parse failed but the lenient one succeeded — Ingest callers surface a warning in that case so the user knows their SKILL.md (or other component .md) is not strict YAML.
The lenient parser exists because Claude Code itself reads frontmatter that way: a SKILL.md whose description contains an unquoted "Triggers on: X, Y" parses fine in claude.ai but breaks sigs.k8s.io/yaml ("mapping values are not allowed in this context"). Without this fallback, the silent `continue` in adapter Ingest dropped any skill with such a description.
func SkillFileOps ¶
SkillFileOps projects each skill into FileOps under skillsDir: the SKILL.md (frontmatter + body) plus one verbatim op per bundled file (scripts/, references/, assets/, …), preserving each bundled file's mode. It is shared by every adapter that writes skills to a skills directory (Claude, OpenCode, Codex) so the "a skill is a directory, not just SKILL.md" projection stays identical across them — when two adapters target the same skills dir, the render pipeline dedupes the byte-identical ops per path.
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter implements adapter.Adapter for Claude Code.
func (*Adapter) Apply ¶
Apply executes ops against Claude's native destinations. All writes route through the supplied DestWriter; we never call iox.AtomicWrite or os.Remove directly here. The DestWriter owns the foreign-collision backup invariant.
func (*Adapter) Capabilities ¶
func (a *Adapter) Capabilities() adapter.Capability
func (*Adapter) Ingest ¶
Ingest reads native Claude config files and returns a partial source.Canonical. It is the inverse of Render: Ingest(Apply(Render(c))) round-trips to c for the components agentsync manages.
func (*Adapter) IngestPlugins ¶
func (a *Adapter) IngestPlugins(scope adapter.Scope, project string) ([]adapter.NativeMarketplace, []adapter.NativePlugin, error)
IngestPlugins discovers the marketplaces and enabled plugins recorded in Claude's native settings.json — the documented `extraKnownMarketplaces` and `enabledPlugins` keys. It is the read side of plugin `import`: the CLI maps each result onto an agentsync marketplace source and replays `marketplace add` + `plugin install` to capture them.
The built-in `claude-plugins-official` marketplace is auto-available in Claude and is NOT listed in extraKnownMarketplaces, so it has no resolvable source here. The CLI resolves such a marketplace from agentsync's own registered marketplaces instead (run `agentsync marketplace add <source>`); only a marketplace registered in neither place is warned about and skipped.
Parsing is lenient: a missing settings.json yields no plugins, and a malformed one is treated as "no plugins discovered" rather than failing the whole import (matching the hooks/LSP blocks in Ingest). Only a genuine read error (e.g. a permission problem) is surfaced.
func (*Adapter) KeyMergeStrategy ¶
KeyMergeStrategy is claude's single key-merge strategy: strict JSON (.claude.json, settings.json).
func (*Adapter) Render ¶
func (a *Adapter) Render(r secrets.Resolved, scope adapter.Scope, project string) ([]adapter.FileOp, []adapter.Skip, error)
Render produces the full set of FileOps for a given resolved canonical model. Pure function: returns the same output for the same input (disk reads are treated as fixed inputs for the purposes of the merge-json-keys strategy).
func (*Adapter) SetStderr ¶
SetStderr replaces the warning sink the adapter writes Ingest warnings to, so a CLI command can route adapter warnings through the same styled writer it uses for its own output. Adapters built via the registry default to os.Stderr; commands that wrap stderr (e.g. `import` styling "warning:" labels) call this to redirect.
type Options ¶
type Options struct {
TargetRoot string // honors AGENTSYNC_TARGET_ROOT (real "/Users/x" in production)
// LookPath overrides exec.LookPath for testing. nil means use exec.LookPath.
LookPath func(file string) (string, error)
// Stderr receives Ingest warnings (lenient-YAML notices, dropped components).
// nil means os.Stderr. Tests inject a bytes.Buffer to assert on warnings.
Stderr io.Writer
}
Options configure the adapter at construction.
type Paths ¶
type Paths struct {
Home string // ~/.claude
Settings string // ~/.claude/settings.json
DotClaude string // ~/.claude.json (user-scope mcpServers + plugin enables live here)
MCPProject string // <proj>/.mcp.json (project-scope mcpServers; empty at user scope)
SkillsDir string // ~/.claude/skills
AgentsDir string // ~/.claude/agents
CommandsDir string // ~/.claude/commands
Memory string // ~/.claude/CLAUDE.md (user scope) or <proj>/CLAUDE.md (project scope)
PluginsCacheDir string // ~/.claude/plugins/cache
}
Paths resolves the destination paths for a given (scope, project, target-root).