mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 08:03:39 +00:00
feat(coding-agent): add --skills CLI flag for filtering skills
Adds glob pattern support for skill filtering: - --skills <patterns> CLI flag (comma-separated glob patterns) - includeSkills setting in settings.json - ignoredSkills now supports glob patterns - ignoredSkills takes precedence over includeSkills and --skills Closes #268
This commit is contained in:
parent
70440f7591
commit
d95a5c4186
8 changed files with 148 additions and 4 deletions
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
- **Process suspension**: Press `Ctrl+Z` to suspend pi and return to the shell. Resume with `fg` as usual. ([#267](https://github.com/badlogic/pi-mono/pull/267) by [@aliou](https://github.com/aliou))
|
||||
|
||||
- **Configurable skills directories**: Added granular control over skill sources with `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject` toggles, plus `customDirectories` and `ignoredSkills` settings. ([#269](https://github.com/badlogic/pi-mono/pull/269) by [@nicobailon](https://github.com/nicobailon))
|
||||
|
||||
- **Skills CLI filtering**: Added `--skills <patterns>` flag for filtering skills with glob patterns. Also added `includeSkills` setting and glob pattern support for `ignoredSkills`. ([#268](https://github.com/badlogic/pi-mono/issues/268))
|
||||
|
||||
## [0.25.2] - 2025-12-21
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -733,6 +733,7 @@ pi [options] [@files...] [messages...]
|
|||
| `--thinking <level>` | Thinking level: `off`, `minimal`, `low`, `medium`, `high` |
|
||||
| `--hook <path>` | Load a hook file (can be used multiple times) |
|
||||
| `--no-skills` | Disable skills discovery and loading |
|
||||
| `--skills <patterns>` | Comma-separated glob patterns to filter skills (e.g., `git-*,docker`) |
|
||||
| `--export <file> [output]` | Export session to HTML |
|
||||
| `--help`, `-h` | Show help |
|
||||
| `--version`, `-v` | Show version |
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ Configure skill loading in `~/.pi/agent/settings.json`:
|
|||
"enablePiUser": true,
|
||||
"enablePiProject": true,
|
||||
"customDirectories": ["~/my-skills-repo"],
|
||||
"ignoredSkills": ["deprecated-skill"]
|
||||
"ignoredSkills": ["deprecated-skill"],
|
||||
"includeSkills": ["git-*", "docker"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -173,7 +174,27 @@ Configure skill loading in `~/.pi/agent/settings.json`:
|
|||
| `enablePiUser` | `true` | Load from `~/.pi/agent/skills/` |
|
||||
| `enablePiProject` | `true` | Load from `<cwd>/.pi/skills/` |
|
||||
| `customDirectories` | `[]` | Additional directories to scan (supports `~` expansion) |
|
||||
| `ignoredSkills` | `[]` | Skill names to exclude |
|
||||
| `ignoredSkills` | `[]` | Glob patterns to exclude (e.g., `["deprecated-*", "test-skill"]`) |
|
||||
| `includeSkills` | `[]` | Glob patterns to include (empty = all; e.g., `["git-*", "docker"]`) |
|
||||
|
||||
**Note:** `ignoredSkills` takes precedence over both `includeSkills` in settings and the `--skills` CLI flag. A skill matching any ignore pattern will be excluded regardless of include patterns.
|
||||
|
||||
### CLI Filtering
|
||||
|
||||
Use `--skills` to filter skills for a specific invocation:
|
||||
|
||||
```bash
|
||||
# Only load specific skills
|
||||
pi --skills git,docker
|
||||
|
||||
# Glob patterns
|
||||
pi --skills "git-*,docker-*"
|
||||
|
||||
# All skills matching a prefix
|
||||
pi --skills "aws-*"
|
||||
```
|
||||
|
||||
This overrides the `includeSkills` setting for the current session.
|
||||
|
||||
## How Skills Work
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export interface Args {
|
|||
print?: boolean;
|
||||
export?: string;
|
||||
noSkills?: boolean;
|
||||
skills?: string[];
|
||||
listModels?: string | true;
|
||||
messages: string[];
|
||||
fileArgs: string[];
|
||||
|
|
@ -115,6 +116,9 @@ export function parseArgs(args: string[]): Args {
|
|||
result.customTools.push(args[++i]);
|
||||
} else if (arg === "--no-skills") {
|
||||
result.noSkills = true;
|
||||
} else if (arg === "--skills" && i + 1 < args.length) {
|
||||
// Comma-separated glob patterns for skill filtering
|
||||
result.skills = args[++i].split(",").map((s) => s.trim());
|
||||
} else if (arg === "--list-models") {
|
||||
// Check if next arg is a search pattern (not a flag or file arg)
|
||||
if (i + 1 < args.length && !args[i + 1].startsWith("-") && !args[i + 1].startsWith("@")) {
|
||||
|
|
@ -157,6 +161,7 @@ ${chalk.bold("Options:")}
|
|||
--hook <path> Load a hook file (can be used multiple times)
|
||||
--tool <path> Load a custom tool file (can be used multiple times)
|
||||
--no-skills Disable skills discovery and loading
|
||||
--skills <patterns> Comma-separated glob patterns to filter skills (e.g., git-*,docker)
|
||||
--export <file> Export session file to HTML and exit
|
||||
--list-models [search] List available models (with optional fuzzy search)
|
||||
--help, -h Show this help
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ export interface SkillsSettings {
|
|||
enablePiUser?: boolean; // default: true
|
||||
enablePiProject?: boolean; // default: true
|
||||
customDirectories?: string[]; // default: []
|
||||
ignoredSkills?: string[]; // default: []
|
||||
ignoredSkills?: string[]; // default: [] (glob patterns to exclude; takes precedence over includeSkills)
|
||||
includeSkills?: string[]; // default: [] (empty = include all; glob patterns to filter)
|
||||
}
|
||||
|
||||
export interface TerminalSettings {
|
||||
|
|
@ -270,6 +271,7 @@ export class SettingsManager {
|
|||
enablePiProject: this.settings.skills?.enablePiProject ?? true,
|
||||
customDirectories: this.settings.skills?.customDirectories ?? [],
|
||||
ignoredSkills: this.settings.skills?.ignoredSkills ?? [],
|
||||
includeSkills: this.settings.skills?.includeSkills ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { existsSync, readdirSync, readFileSync } from "fs";
|
||||
import { minimatch } from "minimatch";
|
||||
import { homedir } from "os";
|
||||
import { basename, dirname, join, resolve } from "path";
|
||||
import { CONFIG_DIR_NAME } from "../config.js";
|
||||
|
|
@ -325,16 +326,34 @@ export function loadSkills(options: SkillsSettings = {}): LoadSkillsResult {
|
|||
enablePiProject = true,
|
||||
customDirectories = [],
|
||||
ignoredSkills = [],
|
||||
includeSkills = [],
|
||||
} = options;
|
||||
|
||||
const skillMap = new Map<string, Skill>();
|
||||
const allWarnings: SkillWarning[] = [];
|
||||
const collisionWarnings: SkillWarning[] = [];
|
||||
|
||||
// Check if skill name matches any of the include patterns
|
||||
function matchesIncludePatterns(name: string): boolean {
|
||||
if (includeSkills.length === 0) return true; // No filter = include all
|
||||
return includeSkills.some((pattern) => minimatch(name, pattern));
|
||||
}
|
||||
|
||||
// Check if skill name matches any of the ignore patterns
|
||||
function matchesIgnorePatterns(name: string): boolean {
|
||||
if (ignoredSkills.length === 0) return false;
|
||||
return ignoredSkills.some((pattern) => minimatch(name, pattern));
|
||||
}
|
||||
|
||||
function addSkills(result: LoadSkillsResult) {
|
||||
allWarnings.push(...result.warnings);
|
||||
for (const skill of result.skills) {
|
||||
if (ignoredSkills.includes(skill.name)) {
|
||||
// Apply ignore filter (glob patterns) - takes precedence over include
|
||||
if (matchesIgnorePatterns(skill.name)) {
|
||||
continue;
|
||||
}
|
||||
// Apply include filter (glob patterns)
|
||||
if (!matchesIncludePatterns(skill.name)) {
|
||||
continue;
|
||||
}
|
||||
const existing = skillMap.get(skill.name);
|
||||
|
|
|
|||
|
|
@ -289,6 +289,9 @@ export async function main(args: string[]) {
|
|||
if (parsed.noSkills) {
|
||||
skillsSettings.enabled = false;
|
||||
}
|
||||
if (parsed.skills && parsed.skills.length > 0) {
|
||||
skillsSettings.includeSkills = parsed.skills;
|
||||
}
|
||||
const systemPrompt = buildSystemPrompt({
|
||||
customPrompt: parsed.systemPrompt,
|
||||
selectedTools: parsed.tools,
|
||||
|
|
|
|||
|
|
@ -258,6 +258,34 @@ describe("skills", () => {
|
|||
expect(skills).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should support glob patterns in ignoredSkills", () => {
|
||||
const { skills } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
enableClaudeUser: false,
|
||||
enableClaudeProject: false,
|
||||
enablePiUser: false,
|
||||
enablePiProject: false,
|
||||
customDirectories: [fixturesDir],
|
||||
ignoredSkills: ["valid-*"],
|
||||
});
|
||||
expect(skills.every((s) => !s.name.startsWith("valid-"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should have ignoredSkills take precedence over includeSkills", () => {
|
||||
const { skills } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
enableClaudeUser: false,
|
||||
enableClaudeProject: false,
|
||||
enablePiUser: false,
|
||||
enablePiProject: false,
|
||||
customDirectories: [fixturesDir],
|
||||
includeSkills: ["valid-*"],
|
||||
ignoredSkills: ["valid-skill"],
|
||||
});
|
||||
// valid-skill should be excluded even though it matches includeSkills
|
||||
expect(skills.every((s) => s.name !== "valid-skill")).toBe(true);
|
||||
});
|
||||
|
||||
it("should expand ~ in customDirectories", () => {
|
||||
const homeSkillsDir = join(homedir(), ".pi/agent/skills");
|
||||
const { skills: withTilde } = loadSkills({
|
||||
|
|
@ -289,6 +317,67 @@ describe("skills", () => {
|
|||
});
|
||||
expect(skills).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should filter skills with includeSkills glob patterns", () => {
|
||||
// Load all skills from fixtures
|
||||
const { skills: allSkills } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
enableClaudeUser: false,
|
||||
enableClaudeProject: false,
|
||||
enablePiUser: false,
|
||||
enablePiProject: false,
|
||||
customDirectories: [fixturesDir],
|
||||
});
|
||||
expect(allSkills.length).toBeGreaterThan(0);
|
||||
|
||||
// Filter to only include "valid-skill"
|
||||
const { skills: filtered } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
enableClaudeUser: false,
|
||||
enableClaudeProject: false,
|
||||
enablePiUser: false,
|
||||
enablePiProject: false,
|
||||
customDirectories: [fixturesDir],
|
||||
includeSkills: ["valid-skill"],
|
||||
});
|
||||
expect(filtered).toHaveLength(1);
|
||||
expect(filtered[0].name).toBe("valid-skill");
|
||||
});
|
||||
|
||||
it("should support glob patterns in includeSkills", () => {
|
||||
const { skills } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
enableClaudeUser: false,
|
||||
enableClaudeProject: false,
|
||||
enablePiUser: false,
|
||||
enablePiProject: false,
|
||||
customDirectories: [fixturesDir],
|
||||
includeSkills: ["valid-*"],
|
||||
});
|
||||
expect(skills.length).toBeGreaterThan(0);
|
||||
expect(skills.every((s) => s.name.startsWith("valid-"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should return all skills when includeSkills is empty", () => {
|
||||
const { skills: withEmpty } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
enableClaudeUser: false,
|
||||
enableClaudeProject: false,
|
||||
enablePiUser: false,
|
||||
enablePiProject: false,
|
||||
customDirectories: [fixturesDir],
|
||||
includeSkills: [],
|
||||
});
|
||||
const { skills: withoutOption } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
enableClaudeUser: false,
|
||||
enableClaudeProject: false,
|
||||
enablePiUser: false,
|
||||
enablePiProject: false,
|
||||
customDirectories: [fixturesDir],
|
||||
});
|
||||
expect(withEmpty.length).toBe(withoutOption.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("collision handling", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue