Add Agent Skills support and CLI commands#441
Add Agent Skills support and CLI commands#441jasonkneen wants to merge 2 commits intolmstudio-ai:mainfrom
Conversation
Introduces Agent Skills (agent.md/SKILL.md) support to the CLI, including interactive skill scaffolding (`lms init`), skill discovery, validation, inspection, and directory management (`lms skills` command group). Integrates skills into chat sessions with system prompt injection, updates core types, parser, and CLI preferences, and registers new commands in the CLI. All changes follow the Agent Skills open specification and maintain compatibility with the broader skills ecosystem.
|
All contributors have signed the CLA ✍️ ✅ |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f6ec96d082
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ...(loadedSkills ?? []).map( | ||
| (skill): SlashCommand => ({ | ||
| name: skill.metadata.name, | ||
| description: skill.metadata.description, |
There was a problem hiding this comment.
Prevent skills from overriding built-in slash commands
Because skills are appended to the slash-command list after the built-ins, and SlashCommandHandler.setCommands stores commands in a Map by name (last registration wins), any skill whose name matches a built-in command will silently replace it. For example, a skill named model (valid by the current schema) will make /model unreachable, which breaks existing chat workflows for those users. Consider rejecting conflicting skill names or ensuring built-ins take precedence.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This pull request introduces comprehensive Agent Skills support to the LM Studio CLI, implementing the Agent Skills open specification (agentskills.io). The changes enable users to create, discover, validate, and use reusable AI agent instructions through a structured SKILL.md format.
Changes:
- Adds interactive skill scaffolding via
lms initcommand with YAML frontmatter validation - Implements
lms skillscommand group (ls, validate, inspect) for skill management and discovery - Integrates discovered skills into chat sessions through system prompt injection and dynamic slash commands
Reviewed changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/skills/types.ts | Defines Zod schemas for skill metadata validation with naming constraints |
| src/skills/parser.ts | Parses SKILL.md files with YAML frontmatter extraction |
| src/skills/discovery.ts | Recursively discovers valid skills from configured directories |
| src/skills/promptBuilder.ts | Builds XML-formatted skill metadata for system prompt injection |
| src/subcommands/init.ts | Interactive skill scaffolding with template generation |
| src/subcommands/skills/validate.ts | Validates skill directories against specification |
| src/subcommands/skills/list.ts | Lists discovered skills with metadata |
| src/subcommands/skills/inspect.ts | Shows detailed skill information and statistics |
| src/subcommands/skills/index.ts | Registers skills command group |
| src/subcommands/chat/index.tsx | Loads skills and injects into system prompt |
| src/subcommands/chat/react/Chat.tsx | Refactors prediction logic for reuse by slash commands |
| src/subcommands/chat/react/slashCommands.ts | Registers skills as dynamic slash commands |
| src/cliPref.ts | Adds skillsDirectories configuration option |
| src/lmstudioPaths.ts | Defines default skills directory path |
| src/index.ts | Registers new Skills command group in CLI |
| .gitignore | Excludes /docs/plans directory |
| patches/* | Third-party dependency patches (informational) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ); | ||
| } | ||
| logger.error("Description is required. Use --description <desc>"); | ||
| throw process.exit(1); |
There was a problem hiding this comment.
The statement throw process.exit(1) is problematic because process.exit() never returns and doesn't throw an exception. It terminates the process immediately. Using throw here doesn't make sense and could confuse TypeScript's control flow analysis. Simply call process.exit(1) directly without throw.
| const currentPrompt = chatRef.current.getSystemPrompt(); | ||
| const isAlreadyActive = currentPrompt.includes( | ||
| `<skill-active>${skill.metadata.name}</skill-active>`, | ||
| ); |
There was a problem hiding this comment.
The check for whether a skill is already active uses a simple string search in the system prompt, which could produce false positives if the skill name appears elsewhere in the prompt (e.g., in regular text or as part of another skill name). Consider using a more precise check, such as a regex pattern that ensures the skill tag is a complete match or tracking active skills in a separate data structure.
| const skillContext = | ||
| `\n\n<skill-active>${skill.metadata.name}</skill-active>\n` + | ||
| `<skill-instructions>\n${skill.body}\n</skill-instructions>`; |
There was a problem hiding this comment.
The skill body is injected directly into XML tags without escaping. If the skill body contains XML-like characters (e.g., <, >, &), this could break the XML structure or cause parsing issues. Consider escaping the skill body content before embedding it in the <skill-instructions> tags, or wrapping it in a CDATA section if the content should be treated as literal text.
| ...(loadedSkills ?? []).map( | ||
| (skill): SlashCommand => ({ | ||
| name: skill.metadata.name, |
There was a problem hiding this comment.
There's a built-in slash command named "skills" (line 190) that lists loaded skills. If a user creates a skill directory named "skills", it will also be registered as a slash command with the same name (line 252), causing a naming conflict. Consider either reserving certain command names (like "skills", "help", "exit", "model", "clear", "system-prompt", "stats", "download") or implementing a collision detection mechanism that warns users or prefixes skill commands to avoid conflicts.
| let frontmatter = `---\nname: ${name}\ndescription: ${description}\n`; | ||
| if (license !== undefined) { | ||
| frontmatter += `license: ${license}\n`; | ||
| } | ||
| frontmatter += `---`; |
There was a problem hiding this comment.
The YAML frontmatter is constructed by simple string concatenation without proper YAML escaping. If the description or license contains special YAML characters (e.g., :, ", ', #, newlines), the generated SKILL.md file will have invalid YAML and fail to parse. Consider using a YAML library to serialize the frontmatter (e.g., using the YAML library already imported for parsing) to ensure proper escaping.
| ); | ||
| } | ||
| logger.error("Skill name is required. Usage: lms init <name>"); | ||
| throw process.exit(1); |
There was a problem hiding this comment.
The statement throw process.exit(1) is problematic because process.exit() never returns and doesn't throw an exception. It terminates the process immediately. Using throw here doesn't make sense and could confuse TypeScript's control flow analysis. Simply call process.exit(1) directly without throw.
| import { addCreateClientOptions, createClient, type CreateClientArgs } from "../../createClient.js"; | ||
| import { formatSizeBytes1000 } from "../../formatSizeBytes1000.js"; | ||
| import { addLogLevelOptions, createLogger, type LogLevelArgs } from "../../logLevel.js"; | ||
| import { defaultSkillsFolderPath } from "../../lmstudioPaths.js"; |
There was a problem hiding this comment.
Unused import defaultSkillsFolderPath.
| import { defaultSkillsFolderPath } from "../../lmstudioPaths.js"; |
|
I have read the CLA Document and I hereby sign the CLA |
Introduces Agent Skills (agent.md/SKILL.md) support to the CLI, including interactive skill scaffolding (
lms init), skill discovery, validation, inspection, and directory management (lms skillscommand group). Integrates skills into chat sessions with system prompt injection, updates core types, parser, and CLI preferences, and registers new commands in the CLI. All changes follow the Agent Skills open specification and maintain compatibility with the broader skills ecosystem.