Class: KairosMcp::AnthropicSkillParser

Inherits:
Object
  • Object
show all
Defined in:
lib/kairos_mcp/anthropic_skill_parser.rb

Overview

AnthropicSkillParser: Parses Anthropic skills format (YAML frontmatter + Markdown)

Directory structure:

skill_name/
├── skill_name.md       # Required: YAML frontmatter + Markdown content
├── scripts/            # Optional: Executable scripts (Python, Bash, Node, etc.)
├── assets/             # Optional: Templates, images, CSS, etc.
└── references/         # Optional: Reference materials, datasets

Defined Under Namespace

Classes: SkillEntry

Class Method Summary collapse

Class Method Details

.atomic_write(target_path, content) ⇒ Object

Atomic-rename write. Tempfile in same directory then File.rename to the target. Crash mid-write leaves target either fully replaced or untouched — never truncated. Security-grounded (don’t destroy someone else’s valid record), not durability-grounded.



133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 133

def atomic_write(target_path, content)
  dir = File.dirname(target_path)
  FileUtils.mkdir_p(dir) unless File.directory?(dir)
  tempname = "#{File.basename(target_path)}.tmp.#{Process.pid}.#{SecureRandom.hex(4)}"
  temp_path = File.join(dir, tempname)
  begin
    File.write(temp_path, content)
    File.rename(temp_path, target_path)
  ensure
    File.delete(temp_path) if File.exist?(temp_path)
  end
end

.create(base_dir, name, content, create_subdirs: false) ⇒ SkillEntry

Create a new skill directory with the Anthropic format

Parameters:

  • base_dir (String)

    Base directory (knowledge/ or context/session_id/)

  • name (String)

    Skill name

  • content (String)

    Full content including YAML frontmatter

  • create_subdirs (Boolean) (defaults to: false)

    Whether to create scripts/assets/references dirs

Returns:



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 100

def create(base_dir, name, content, create_subdirs: false)
  skill_dir = File.join(base_dir, name)
  FileUtils.mkdir_p(skill_dir)

  md_file = File.join(skill_dir, "#{name}.md")
  atomic_write(md_file, content)

  if create_subdirs
    FileUtils.mkdir_p(File.join(skill_dir, 'scripts'))
    FileUtils.mkdir_p(File.join(skill_dir, 'assets'))
    FileUtils.mkdir_p(File.join(skill_dir, 'references'))
  end

  parse(skill_dir)
end

.extract_frontmatter(content) ⇒ Array<Hash, String>

Extract frontmatter and body from content

Parameters:

  • content (String)

    Full file content

Returns:

  • (Array<Hash, String>)
    frontmatter, body


213
214
215
216
217
218
219
220
221
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 213

def extract_frontmatter(content)
  if content =~ /\A---\r?\n(.+?)\r?\n---\r?\n(.*)/m
    frontmatter = YAML.safe_load($1, permitted_classes: [Symbol, Date, Time]) || {}
    body = $2
    [frontmatter, body]
  else
    [{}, content]
  end
end

.generate_content(frontmatter, body) ⇒ String

Generate YAML frontmatter + Markdown content

Parameters:

  • frontmatter (Hash)

    Frontmatter data

  • body (String)

    Markdown body content

Returns:

  • (String)

    Complete file content



204
205
206
207
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 204

def generate_content(frontmatter, body)
  yaml_str = frontmatter.to_yaml.sub(/^---\n/, '')
  "---\n#{yaml_str}---\n\n#{body}"
end

.list_assets(skill) ⇒ Array<Hash>

List all assets in a skill

Parameters:

Returns:

  • (Array<Hash>)

    List of asset info



167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 167

def list_assets(skill)
  return [] unless skill.has_assets?

  Dir[File.join(skill.assets_path, '**/*')].select { |f| File.file?(f) }.map do |f|
    {
      name: File.basename(f),
      path: f,
      relative_path: f.sub(skill.assets_path + '/', ''),
      size: File.size(f),
      extension: File.extname(f)
    }
  end
end

.list_references(skill) ⇒ Array<Hash>

List all references in a skill

Parameters:

Returns:

  • (Array<Hash>)

    List of reference info



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 185

def list_references(skill)
  return [] unless skill.has_references?

  Dir[File.join(skill.references_path, '**/*')].select { |f| File.file?(f) }.map do |f|
    {
      name: File.basename(f),
      path: f,
      relative_path: f.sub(skill.references_path + '/', ''),
      size: File.size(f),
      extension: File.extname(f)
    }
  end
end

.list_scripts(skill) ⇒ Array<Hash>

List all scripts in a skill

Parameters:

Returns:

  • (Array<Hash>)

    List of script info



150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 150

def list_scripts(skill)
  return [] unless skill.has_scripts?

  Dir[File.join(skill.scripts_path, '*')].map do |f|
    {
      name: File.basename(f),
      path: f,
      executable: File.executable?(f),
      size: File.size(f)
    }
  end
end

.parse(skill_dir) ⇒ SkillEntry?

Parse a skill directory and return a SkillEntry

Parameters:

  • skill_dir (String)

    Path to the skill directory

Returns:

  • (SkillEntry, nil)

    Parsed skill entry or nil if invalid



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 66

def parse(skill_dir)
  return nil unless File.directory?(skill_dir)

  md_file = find_md_file(skill_dir)
  return nil unless md_file

  content = File.read(md_file, encoding: 'UTF-8')
  frontmatter, body = extract_frontmatter(content)

  skill_name = File.basename(skill_dir)

  SkillEntry.new(
    name: frontmatter['name'] || skill_name,
    description: frontmatter['description'],
    version: frontmatter['version'],
    layer: frontmatter['layer'],
    tags: normalize_tags(frontmatter['tags']),
    content: body.strip,
    frontmatter: frontmatter,
    base_path: skill_dir,
    md_file_path: md_file,
    scripts_path: File.join(skill_dir, 'scripts'),
    assets_path: File.join(skill_dir, 'assets'),
    references_path: File.join(skill_dir, 'references')
  )
end

.update(skill_dir, new_content) ⇒ SkillEntry

Update an existing skill’s content

Parameters:

  • skill_dir (String)

    Path to the skill directory

  • new_content (String)

    New content including YAML frontmatter

Returns:



121
122
123
124
125
126
127
# File 'lib/kairos_mcp/anthropic_skill_parser.rb', line 121

def update(skill_dir, new_content)
  md_file = find_md_file(skill_dir)
  raise "No markdown file found in #{skill_dir}" unless md_file

  atomic_write(md_file, new_content)
  parse(skill_dir)
end