feat(skills): add invocation types and parser support

Add types for skill invocation control:
- SkillInvocationPolicy for user-invocable/model-invocable flags
- SkillCommandSpec for command specifications
- SkillCommandDispatch for tool dispatch configuration
- SkillInvocationResult for resolved command results

Update parser to handle frontmatter fields:
- user-invocable (kebab-case, camelCase, snake_case)
- disable-model-invocation
- command-dispatch, command-tool, command-arg-mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jiang Bohan 2026-01-30 16:10:59 +08:00
parent 578c8a6534
commit 8fbd72c329
2 changed files with 121 additions and 0 deletions

View file

@ -89,9 +89,61 @@ function validateFrontmatter(raw: Record<string, unknown>): SkillFrontmatter | n
};
}
// Parse invocation control fields
// Support both kebab-case and camelCase for compatibility
const userInvocableRaw =
raw["user-invocable"] ?? raw["userInvocable"] ?? raw["user_invocable"];
if (typeof userInvocableRaw === "boolean") {
frontmatter.userInvocable = userInvocableRaw;
} else if (typeof userInvocableRaw === "string") {
frontmatter.userInvocable = parseBooleanString(userInvocableRaw);
}
const disableModelRaw =
raw["disable-model-invocation"] ??
raw["disableModelInvocation"] ??
raw["disable_model_invocation"];
if (typeof disableModelRaw === "boolean") {
frontmatter.disableModelInvocation = disableModelRaw;
} else if (typeof disableModelRaw === "string") {
frontmatter.disableModelInvocation = parseBooleanString(disableModelRaw);
}
// Parse command dispatch fields
const dispatchRaw =
raw["command-dispatch"] ?? raw["commandDispatch"] ?? raw["command_dispatch"];
if (typeof dispatchRaw === "string") {
frontmatter.commandDispatch = dispatchRaw.trim().toLowerCase();
}
const toolRaw = raw["command-tool"] ?? raw["commandTool"] ?? raw["command_tool"];
if (typeof toolRaw === "string") {
frontmatter.commandTool = toolRaw.trim();
}
const argModeRaw =
raw["command-arg-mode"] ?? raw["commandArgMode"] ?? raw["command_arg_mode"];
if (typeof argModeRaw === "string") {
frontmatter.commandArgMode = argModeRaw.trim().toLowerCase();
}
return frontmatter;
}
/**
* Parse boolean from string value
*/
function parseBooleanString(value: string): boolean | undefined {
const normalized = value.trim().toLowerCase();
if (normalized === "true" || normalized === "yes" || normalized === "1") {
return true;
}
if (normalized === "false" || normalized === "no" || normalized === "0") {
return false;
}
return undefined;
}
/**
* Parse a SKILL.md file into a Skill object
*

View file

@ -106,6 +106,20 @@ export interface SkillFrontmatter {
homepage?: string | undefined;
/** Skill-specific metadata */
metadata?: SkillMetadata | undefined;
// Invocation control fields
/** Whether users can invoke via /command (default: true) */
userInvocable?: boolean | undefined;
/** Whether to exclude from AI system prompt (default: false) */
disableModelInvocation?: boolean | undefined;
// Command dispatch fields
/** Command dispatch mode (e.g., "tool") */
commandDispatch?: string | undefined;
/** Tool name for dispatch (when commandDispatch: "tool") */
commandTool?: string | undefined;
/** Argument mode for dispatch (default: "raw") */
commandArgMode?: string | undefined;
}
/**
@ -222,6 +236,61 @@ export interface EligibilityResult {
reasons?: string[] | undefined;
}
// ============================================================================
// Invocation Types
// ============================================================================
/**
* Skill invocation policy
* Controls how a skill can be invoked
*/
export interface SkillInvocationPolicy {
/** Whether users can invoke this skill via /command (default: true) */
userInvocable: boolean;
/** Whether to exclude from AI's system prompt (default: false) */
disableModelInvocation: boolean;
}
/**
* Command dispatch specification
* For skills that dispatch directly to a tool
*/
export interface SkillCommandDispatch {
/** Dispatch type */
kind: "tool";
/** Tool name to invoke */
toolName: string;
/** How to pass arguments (default: "raw") */
argMode?: "raw" | undefined;
}
/**
* Skill command specification
* Represents a user-invocable skill command
*/
export interface SkillCommandSpec {
/** Normalized command name (e.g., "pdf" for /pdf) */
name: string;
/** Original skill name/ID */
skillId: string;
/** Command description */
description: string;
/** Optional dispatch behavior */
dispatch?: SkillCommandDispatch | undefined;
}
/**
* Skill invocation result
*/
export interface SkillInvocationResult {
/** The matched command */
command: SkillCommandSpec;
/** Arguments passed to the command */
args?: string | undefined;
/** The skill instructions to inject */
instructions: string;
}
/**
* Filename constant for skill definition file
*/