Class: Clacky::Skill
- Inherits:
-
Object
- Object
- Clacky::Skill
- Defined in:
- lib/clacky/skill.rb
Overview
Represents a skill with its metadata and content. A skill is defined by a SKILL.md file with optional YAML frontmatter.
Constant Summary collapse
- FRONTMATTER_FIELDS =
Frontmatter fields that are recognized
%w[ name name_zh description description_zh disable-model-invocation user-invocable allowed-tools context agent argument-hint hooks fork_agent model forbidden_tools auto_summarize ].freeze
- DESCRIPTION_MAX_CHARS =
Maximum length for a skill’s description when injected into the system prompt. Descriptions longer than this are truncated to protect the token budget — a good description is a trigger hint, not a tutorial. Authors still see their full description via ‘skill.description`; only the system-prompt rendering is truncated.
Anthropic’s hard limit is 1024, but empirically ~300 chars is enough for reliable triggering (including trigger-phrase lists); longer content belongs in the SKILL.md body.
300
Instance Attribute Summary collapse
-
#agent_type ⇒ Object
readonly
Returns the value of attribute agent_type.
-
#allowed_tools ⇒ Object
readonly
Returns the value of attribute allowed_tools.
-
#argument_hint ⇒ Object
readonly
Returns the value of attribute argument_hint.
-
#auto_summarize ⇒ Object
readonly
Returns the value of attribute auto_summarize.
-
#brand_config ⇒ Object
readonly
Returns the value of attribute brand_config.
-
#brand_skill ⇒ Object
readonly
Returns the value of attribute brand_skill.
-
#content ⇒ Object
readonly
Returns the value of attribute content.
-
#context ⇒ Object
readonly
Returns the value of attribute context.
-
#description ⇒ Object
readonly
Returns the value of attribute description.
-
#description_zh ⇒ Object
readonly
Returns the value of attribute description_zh.
-
#directory ⇒ Object
readonly
Returns the value of attribute directory.
-
#disable_model_invocation ⇒ Object
readonly
Returns the value of attribute disable_model_invocation.
-
#forbidden_tools ⇒ Object
readonly
Returns the value of attribute forbidden_tools.
-
#fork_agent ⇒ Object
readonly
Returns the value of attribute fork_agent.
-
#frontmatter ⇒ Object
readonly
Returns the value of attribute frontmatter.
-
#hooks ⇒ Object
readonly
Returns the value of attribute hooks.
-
#invalid ⇒ Boolean
readonly
When true the skill has an unrecoverable metadata problem (e.g. directory name is itself an invalid slug).
-
#invalid_reason ⇒ String?
readonly
Human-readable reason why the skill is invalid (nil when valid).
-
#model ⇒ Object
readonly
Returns the value of attribute model.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#name_zh ⇒ Object
readonly
Returns the value of attribute name_zh.
-
#source ⇒ Symbol?
Source location of this skill — set by SkillLoader after registration.
-
#source_path ⇒ Object
readonly
Returns the value of attribute source_path.
-
#user_invocable ⇒ Object
readonly
Returns the value of attribute user_invocable.
-
#warnings ⇒ Array<String>
readonly
Warnings accumulated during load (e.g. name was invalid and fell back to dir name).
Instance Method Summary collapse
-
#agents_scope ⇒ Array<String>
Get the agent scope for this skill.
-
#allowed_for_agent?(profile_name) ⇒ Boolean
Check if this skill is allowed for the given agent profile name.
-
#auto_summarize? ⇒ Boolean
Check if subagent should auto-summarize results.
-
#context_description ⇒ String
Get the description for context loading.
-
#decrypted_content ⇒ String
Decrypt and return the raw skill content.
-
#disabled? ⇒ Boolean
Check if this skill is disabled (disable-model-invocation: true).
-
#encrypted? ⇒ Boolean
Returns true when this skill’s content is stored encrypted on disk.
-
#expand_templates(content, context) ⇒ String
Expand <%= key %> template placeholders via ERB.
- #extract_first_paragraph ⇒ Object
-
#forbidden_tools_list ⇒ Array<String>
Get the list of forbidden tools for the subagent.
-
#fork_agent? ⇒ Boolean
Check if this skill should fork a subagent.
-
#has_supporting_files? ⇒ Boolean
Check if this skill has any supporting files/scripts beyond SKILL.md.
- #has_warnings? ⇒ Boolean
-
#identifier ⇒ String
Get the skill identifier (uses name from frontmatter or directory name).
-
#initialize(directory, source_path: nil, brand_skill: false, brand_config: nil, cached_metadata: nil) ⇒ Skill
constructor
A new instance of Skill.
- #invalid? ⇒ Boolean
- #load_skill ⇒ Object
-
#model_invocation_allowed? ⇒ Boolean
Check if skill can be automatically invoked by the model.
-
#parse_frontmatter(content) ⇒ Object
Parse content that may or may not have YAML frontmatter.
-
#process_content(shell_output: {}, template_context: {}, script_dir: nil) ⇒ String
Process the skill content with argument substitution and template expansion.
-
#read_supporting_file(filename) ⇒ String?
Load content of a supporting file.
-
#sanitize_frontmatter ⇒ Object
Sanitize and auto-correct frontmatter fields instead of raising on bad data.
-
#slash_command ⇒ String
Get the slash command for this skill.
-
#subagent_model ⇒ String?
Get the model to use for the subagent (if fork_agent is true).
-
#supporting_files ⇒ Array<Pathname>
Get all supporting files in the skill directory (excluding SKILL.md) Return plain-text supporting files for this skill (non-encrypted skills only).
-
#to_h ⇒ Hash
Convert to a hash representation.
-
#user_invocable? ⇒ Boolean
Check if skill can be invoked by user via slash command.
Constructor Details
#initialize(directory, source_path: nil, brand_skill: false, brand_config: nil, cached_metadata: nil) ⇒ Skill
Returns a new instance of Skill.
86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/clacky/skill.rb', line 86 def initialize(directory, source_path: nil, brand_skill: false, brand_config: nil, cached_metadata: nil) @directory = Pathname.new(directory) @source_path = source_path ? Pathname.new(source_path) : @directory @brand_skill = brand_skill @brand_config = brand_config @cached_metadata = @encrypted = false @warnings = [] @invalid = false @invalid_reason = nil load_skill end |
Instance Attribute Details
#agent_type ⇒ Object (readonly)
Returns the value of attribute agent_type.
35 36 37 |
# File 'lib/clacky/skill.rb', line 35 def agent_type @agent_type end |
#allowed_tools ⇒ Object (readonly)
Returns the value of attribute allowed_tools.
35 36 37 |
# File 'lib/clacky/skill.rb', line 35 def allowed_tools @allowed_tools end |
#argument_hint ⇒ Object (readonly)
Returns the value of attribute argument_hint.
35 36 37 |
# File 'lib/clacky/skill.rb', line 35 def argument_hint @argument_hint end |
#auto_summarize ⇒ Object (readonly)
Returns the value of attribute auto_summarize.
36 37 38 |
# File 'lib/clacky/skill.rb', line 36 def auto_summarize @auto_summarize end |
#brand_config ⇒ Object (readonly)
Returns the value of attribute brand_config.
37 38 39 |
# File 'lib/clacky/skill.rb', line 37 def brand_config @brand_config end |
#brand_skill ⇒ Object (readonly)
Returns the value of attribute brand_skill.
37 38 39 |
# File 'lib/clacky/skill.rb', line 37 def brand_skill @brand_skill end |
#content ⇒ Object (readonly)
Returns the value of attribute content.
33 34 35 |
# File 'lib/clacky/skill.rb', line 33 def content @content end |
#context ⇒ Object (readonly)
Returns the value of attribute context.
35 36 37 |
# File 'lib/clacky/skill.rb', line 35 def context @context end |
#description ⇒ Object (readonly)
Returns the value of attribute description.
33 34 35 |
# File 'lib/clacky/skill.rb', line 33 def description @description end |
#description_zh ⇒ Object (readonly)
Returns the value of attribute description_zh.
33 34 35 |
# File 'lib/clacky/skill.rb', line 33 def description_zh @description_zh end |
#directory ⇒ Object (readonly)
Returns the value of attribute directory.
32 33 34 |
# File 'lib/clacky/skill.rb', line 32 def directory @directory end |
#disable_model_invocation ⇒ Object (readonly)
Returns the value of attribute disable_model_invocation.
34 35 36 |
# File 'lib/clacky/skill.rb', line 34 def disable_model_invocation @disable_model_invocation end |
#forbidden_tools ⇒ Object (readonly)
Returns the value of attribute forbidden_tools.
36 37 38 |
# File 'lib/clacky/skill.rb', line 36 def forbidden_tools @forbidden_tools end |
#fork_agent ⇒ Object (readonly)
Returns the value of attribute fork_agent.
36 37 38 |
# File 'lib/clacky/skill.rb', line 36 def fork_agent @fork_agent end |
#frontmatter ⇒ Object (readonly)
Returns the value of attribute frontmatter.
32 33 34 |
# File 'lib/clacky/skill.rb', line 32 def frontmatter @frontmatter end |
#hooks ⇒ Object (readonly)
Returns the value of attribute hooks.
35 36 37 |
# File 'lib/clacky/skill.rb', line 35 def hooks @hooks end |
#invalid ⇒ Boolean (readonly)
When true the skill has an unrecoverable metadata problem (e.g. directory name is itself an invalid slug). The skill is still registered so it can be shown in the UI (greyed-out with an explanation), but it is excluded from the system prompt and slash command dispatch.
54 55 56 |
# File 'lib/clacky/skill.rb', line 54 def invalid @invalid end |
#invalid_reason ⇒ String? (readonly)
Human-readable reason why the skill is invalid (nil when valid).
58 59 60 |
# File 'lib/clacky/skill.rb', line 58 def invalid_reason @invalid_reason end |
#model ⇒ Object (readonly)
Returns the value of attribute model.
36 37 38 |
# File 'lib/clacky/skill.rb', line 36 def model @model end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
33 34 35 |
# File 'lib/clacky/skill.rb', line 33 def name @name end |
#name_zh ⇒ Object (readonly)
Returns the value of attribute name_zh.
33 34 35 |
# File 'lib/clacky/skill.rb', line 33 def name_zh @name_zh end |
#source ⇒ Symbol?
Source location of this skill — set by SkillLoader after registration. One of: :default, :global_claude, :global_clacky, :project_claude, :project_clacky, :brand
42 43 44 |
# File 'lib/clacky/skill.rb', line 42 def source @source end |
#source_path ⇒ Object (readonly)
Returns the value of attribute source_path.
32 33 34 |
# File 'lib/clacky/skill.rb', line 32 def source_path @source_path end |
#user_invocable ⇒ Object (readonly)
Returns the value of attribute user_invocable.
34 35 36 |
# File 'lib/clacky/skill.rb', line 34 def user_invocable @user_invocable end |
#warnings ⇒ Array<String> (readonly)
Warnings accumulated during load (e.g. name was invalid and fell back to dir name). Non-empty means the skill loaded but something was auto-corrected.
47 48 49 |
# File 'lib/clacky/skill.rb', line 47 def warnings @warnings end |
Instance Method Details
#agents_scope ⇒ Array<String>
Get the agent scope for this skill. Parsed from the ‘agent:` frontmatter field. Returns an array of agent names, or [“all”] if not specified.
146 147 148 149 150 151 152 153 154 |
# File 'lib/clacky/skill.rb', line 146 def agents_scope return ["all"] if @agent_type.nil? case @agent_type when "all" then ["all"] when Array then @agent_type.map(&:to_s) else [@agent_type.to_s] end end |
#allowed_for_agent?(profile_name) ⇒ Boolean
Check if this skill is allowed for the given agent profile name. Returns true when the skill’s ‘agent:` field is “all” (default) or includes the given profile name.
161 162 163 164 |
# File 'lib/clacky/skill.rb', line 161 def allowed_for_agent?(profile_name) scope = agents_scope scope.include?("all") || scope.include?(profile_name.to_s) end |
#auto_summarize? ⇒ Boolean
Check if subagent should auto-summarize results
138 139 140 |
# File 'lib/clacky/skill.rb', line 138 def auto_summarize? @auto_summarize != false end |
#context_description ⇒ String
Get the description for context loading. Returns the description from frontmatter (or first paragraph of content), hard-capped at DESCRIPTION_MAX_CHARS so a single overlong skill can’t blow up the system prompt. Truncation is marked with an ellipsis.
188 189 190 191 192 193 |
# File 'lib/clacky/skill.rb', line 188 def context_description raw = @description || extract_first_paragraph return raw if raw.nil? || raw.length <= DESCRIPTION_MAX_CHARS raw[0, DESCRIPTION_MAX_CHARS - 1] + "…" end |
#decrypted_content ⇒ String
Decrypt and return the raw skill content.
For brand skills the content lives in SKILL.md.enc and is decrypted in memory via BrandConfig#decrypt_skill_content — it is never written to disk as plain text.
For regular skills this is identical to reading @content directly.
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'lib/clacky/skill.rb', line 337 def decrypted_content return @content unless encrypted? raise "brand_config is required to decrypt brand skill '#{identifier}'" unless @brand_config enc_path = @directory.join("SKILL.md.enc").to_s raw = @brand_config.decrypt_skill_content(enc_path) # Strip frontmatter from the decrypted bytes so callers get only the body if raw.start_with?("---") fm_match = raw.match(/\A---\n.*?\n---\n*/m) fm_match ? raw[fm_match.end(0)..].strip : raw else raw end end |
#disabled? ⇒ Boolean
Check if this skill is disabled (disable-model-invocation: true)
62 63 64 |
# File 'lib/clacky/skill.rb', line 62 def disabled? @disable_model_invocation == true end |
#encrypted? ⇒ Boolean
Returns true when this skill’s content is stored encrypted on disk.
323 324 325 |
# File 'lib/clacky/skill.rb', line 323 def encrypted? @encrypted == true end |
#expand_templates(content, context) ⇒ String
Expand <%= key %> template placeholders via ERB. context is a Hash<String|Symbol, String|Proc> — Proc values are called lazily. Unknown bindings raise no error; ERB just leaves them blank (nil.to_s).
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 |
# File 'lib/clacky/skill.rb', line 556 def (content, context) # Shell-style ${VAR} substitution from ENV — handles variables like # ${CLACKY_SERVER_PORT}, ${CLACKY_SERVER_HOST} used in SKILL.md files. # Unknown variables are left as-is (no substitution). content = content.gsub(/\$\{([A-Z_][A-Z0-9_]*)\}/) { ENV[$1] || $& } return content if context.nil? || context.empty? # Build a lightweight binding that exposes each context key as a local method scope = Object.new context.each do |key, value| resolved = value.respond_to?(:call) ? value.call : value scope.define_singleton_method(key.to_s) { resolved.to_s } scope.define_singleton_method(key.to_sym) { resolved.to_s } end require "erb" ERB.new(content, trim_mode: "-").result(scope.instance_eval { binding }) rescue => e # If ERB fails (e.g. unknown variable), return content as-is content end |
#extract_first_paragraph ⇒ Object
546 547 548 |
# File 'lib/clacky/skill.rb', line 546 def extract_first_paragraph @content.split(/\n\n/).first.to_s end |
#forbidden_tools_list ⇒ Array<String>
Get the list of forbidden tools for the subagent
132 133 134 |
# File 'lib/clacky/skill.rb', line 132 def forbidden_tools_list @forbidden_tools || [] end |
#fork_agent? ⇒ Boolean
Check if this skill should fork a subagent
120 121 122 |
# File 'lib/clacky/skill.rb', line 120 def fork_agent? @fork_agent == true end |
#has_supporting_files? ⇒ Boolean
Check if this skill has any supporting files/scripts beyond SKILL.md. For encrypted skills, checks for .enc files that are not SKILL.md.enc or the manifest. For plain skills, checks for any files other than SKILL.md.
221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/clacky/skill.rb', line 221 def has_supporting_files? return false unless @directory.exist? if encrypted? Dir.glob(File.join(@directory.to_s, "**", "*.enc")).any? do |f| base = File.basename(f) base != "SKILL.md.enc" && base != "MANIFEST.enc.json" end else supporting_files.any? end end |
#has_warnings? ⇒ Boolean
72 73 74 |
# File 'lib/clacky/skill.rb', line 72 def has_warnings? @warnings&.any? end |
#identifier ⇒ String
Get the skill identifier (uses name from frontmatter or directory name)
102 103 104 |
# File 'lib/clacky/skill.rb', line 102 def identifier @name || @directory.basename.to_s end |
#invalid? ⇒ Boolean
67 68 69 |
# File 'lib/clacky/skill.rb', line 67 def invalid? @invalid == true end |
#load_skill ⇒ Object
355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/clacky/skill.rb', line 355 def load_skill if @brand_skill load_brand_skill else load_plain_skill end # Set defaults @user_invocable = true if @user_invocable.nil? @disable_model_invocation = false if @disable_model_invocation.nil? sanitize_frontmatter end |
#model_invocation_allowed? ⇒ Boolean
Check if skill can be automatically invoked by the model
114 115 116 |
# File 'lib/clacky/skill.rb', line 114 def model_invocation_allowed? !@disable_model_invocation end |
#parse_frontmatter(content) ⇒ Object
Parse content that may or may not have YAML frontmatter. This method is lenient: bad frontmatter format or YAML errors just produce warnings rather than raising — the raw text becomes the skill content instead.
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 |
# File 'lib/clacky/skill.rb', line 443 def parse_frontmatter(content) frontmatter_match = content.match(/\A---\n(.*?)\n---[ \t]*\n?/m) if frontmatter_match yaml_content = frontmatter_match[1] begin @frontmatter = YAML.safe_load(yaml_content) || {} rescue Psych::Exception => e # Bad YAML — treat whole file as plain content, record warning @warnings << "Could not parse YAML frontmatter: #{e.}. Treating file as plain content." @frontmatter = {} @content = content extract_fields_from_frontmatter return end @content = content[frontmatter_match.end(0)..-1].to_s.strip else # No valid frontmatter block — treat everything as content (no YAML at all, # or an unclosed --- block). We record a warning only if it looked like the # author tried to write frontmatter but made a mistake. if content.start_with?("---") @warnings << "Frontmatter block started with '---' but no closing '---' was found. Treating file as plain content." end @frontmatter = {} @content = content end extract_fields_from_frontmatter end |
#process_content(shell_output: {}, template_context: {}, script_dir: nil) ⇒ String
Process the skill content with argument substitution and template expansion
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/clacky/skill.rb', line 244 def process_content(shell_output: {}, template_context: {}, script_dir: nil) # For brand skills, decrypt content in memory at invoke time. # For plain skills, use the already-loaded @content. processed_content = decrypted_content.dup # Expand <%= key %> templates processed_content = (processed_content, template_context) # Replace shell command outputs shell_output.each do |command, output| placeholder = "!`#{command}`" processed_content.gsub!(placeholder, output.to_s) end # Append supporting files list if any exist. # When script_dir is given (encrypted skill with decrypted tmpdir), use that # directory for both the path label and the file listing so the LLM sees real # paths it can actually execute. effective_dir = script_dir || @directory.to_s effective_files = if script_dir && Dir.exist?(script_dir) gitignore_path = Utils::FileIgnoreHelper.find_gitignore(script_dir) gitignore = gitignore_path ? GitignoreParser.new(gitignore_path) : nil Dir.glob(File.join(script_dir, "**", "*")) .reject { |f| File.directory?(f) } .reject { |f| Utils::FileIgnoreHelper.should_ignore_file?(f, script_dir, gitignore) } .map { |f| f.sub("#{script_dir}/", "") } .sort else supporting_files.map { |p| p.relative_path_from(@directory).to_s } end if effective_files.any? max_files = 20 truncated = effective_files.length > max_files listed_files = effective_files.first(max_files) processed_content += "\n\n## Supporting Files\n\n" processed_content += "The following files are available in this skill's directory (`#{effective_dir}`):\n\n" listed_files.each do |file| processed_content += "- `#{file}`\n" end if truncated processed_content += "\n_(#{effective_files.length - max_files} more files not shown)_\n" end end processed_content end |
#read_supporting_file(filename) ⇒ String?
Load content of a supporting file
316 317 318 319 |
# File 'lib/clacky/skill.rb', line 316 def read_supporting_file(filename) file_path = @directory.join(filename) file_path.exist? ? file_path.read : nil end |
#sanitize_frontmatter ⇒ Object
Sanitize and auto-correct frontmatter fields instead of raising on bad data. Skills should always load — invalid fields are corrected with a warning, or the skill is marked @invalid so the UI can display it greyed-out.
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 |
# File 'lib/clacky/skill.rb', line 497 def sanitize_frontmatter dir_slug = @directory.basename.to_s valid_slug = ->(s) { s.to_s.match?(/\A[a-z0-9][a-z0-9_-]*\z/) } # --- name --- # Brand skills loaded via cached_metadata have their name pre-sanitized by # record_installed_skill (brand_config.rb) — skip slug validation for them. # The frontmatter name (e.g. "Antique Identifier") is the human-readable display # name and should not be treated as a slug. if @cached_metadata @name ||= dir_slug elsif @name name_invalid = !valid_slug.call(@name) || @name.length > 64 if name_invalid if valid_slug.call(dir_slug) # Recoverable: fall back to directory name, record a warning @warnings << "Invalid name '#{@name}' in metadata; using directory name '#{dir_slug}' instead." @name = dir_slug else # Unrecoverable: both name and directory slug are invalid — mark skill as invalid @invalid = true @invalid_reason = "Invalid skill name '#{@name}' and directory name '#{dir_slug}' is also not a valid slug. " \ "Expected lowercase letters, numbers, and hyphens (e.g. 'my-skill')." @name = nil end end else # No name in frontmatter — check the directory slug itself unless valid_slug.call(dir_slug) @invalid = true @invalid_reason = "Directory name '#{dir_slug}' is not a valid skill slug. " \ "Expected lowercase letters, numbers, and hyphens (e.g. 'my-skill')." end end # --- forbidden_tools --- if @forbidden_tools && !@forbidden_tools.is_a?(Array) @warnings << "forbidden_tools must be an array; ignoring value: #{@forbidden_tools.inspect}" @forbidden_tools = nil end # --- allowed-tools --- if @allowed_tools && !@allowed_tools.is_a?(Array) @warnings << "allowed-tools must be an array; ignoring value: #{@allowed_tools.inspect}" @allowed_tools = nil end end |
#slash_command ⇒ String
Get the slash command for this skill
168 169 170 |
# File 'lib/clacky/skill.rb', line 168 def slash_command "/#{identifier}" end |
#subagent_model ⇒ String?
Get the model to use for the subagent (if fork_agent is true)
126 127 128 |
# File 'lib/clacky/skill.rb', line 126 def subagent_model @model end |
#supporting_files ⇒ Array<Pathname>
Get all supporting files in the skill directory (excluding SKILL.md) Return plain-text supporting files for this skill (non-encrypted skills only). For encrypted skills this always returns [] — the decrypted files live in a tmpdir created by SkillManager at invoke time (see has_supporting_scripts?).
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/clacky/skill.rb', line 201 def supporting_files return [] unless @directory.exist? return [] if encrypted? dir = @directory.to_s gitignore_path = Utils::FileIgnoreHelper.find_gitignore(dir) gitignore = gitignore_path ? GitignoreParser.new(gitignore_path) : nil Dir.glob(File.join(dir, "**", "*")) .reject { |f| File.directory?(f) } .reject { |f| File.basename(f) == "SKILL.md" } .reject { |f| Utils::FileIgnoreHelper.should_ignore_file?(f, dir, gitignore) } .map { |f| Pathname.new(f) } .sort end |
#to_h ⇒ Hash
Convert to a hash representation
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/clacky/skill.rb', line 295 def to_h { name: identifier, name_zh: @name_zh, description: context_description, directory: @directory.to_s, source_path: @source_path.to_s, user_invocable: user_invocable?, model_invocation_allowed: model_invocation_allowed?, fork_agent: fork_agent?, subagent_model: @model, forbidden_tools: @forbidden_tools, allowed_tools: @allowed_tools, argument_hint: @argument_hint, content_length: encrypted? ? nil : @content&.length } end |
#user_invocable? ⇒ Boolean
Check if skill can be invoked by user via slash command
108 109 110 |
# File 'lib/clacky/skill.rb', line 108 def user_invocable? @user_invocable.nil? || @user_invocable end |