@indiccoder/mentis-cli 1.0.5 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6 -0
- package/dist/repl/ReplManager.js +160 -17
- package/dist/skills/LoadSkillTool.js +133 -0
- package/dist/skills/Skill.js +6 -0
- package/dist/skills/SkillCreator.js +247 -0
- package/dist/skills/SkillsManager.js +337 -0
- package/dist/ui/UIManager.js +46 -8
- package/dist/utils/UpdateManager.js +81 -0
- package/docs/SKILLS.md +319 -0
- package/examples/skills/code-reviewer/SKILL.md +88 -0
- package/examples/skills/commit-helper/SKILL.md +66 -0
- package/examples/skills/pdf-processing/SKILL.md +108 -0
- package/package.json +3 -2
- package/src/index.ts +7 -0
- package/src/repl/ReplManager.ts +193 -17
- package/src/skills/LoadSkillTool.ts +168 -0
- package/src/skills/Skill.ts +51 -0
- package/src/skills/SkillCreator.ts +237 -0
- package/src/skills/SkillsManager.ts +354 -0
- package/src/ui/UIManager.ts +61 -15
- package/src/utils/UpdateManager.ts +83 -0
package/src/repl/ReplManager.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { Tool } from '../tools/Tool';
|
|
|
17
17
|
import { McpClient } from '../mcp/McpClient';
|
|
18
18
|
|
|
19
19
|
import { CheckpointManager } from '../checkpoint/CheckpointManager';
|
|
20
|
+
import { SkillsManager } from '../skills/SkillsManager';
|
|
21
|
+
import { LoadSkillTool, ListSkillsTool, ReadSkillFileTool } from '../skills/LoadSkillTool';
|
|
20
22
|
import * as readline from 'readline';
|
|
21
23
|
import * as fs from 'fs';
|
|
22
24
|
import * as path from 'path';
|
|
@@ -31,18 +33,23 @@ export class ReplManager {
|
|
|
31
33
|
private modelClient!: ModelClient;
|
|
32
34
|
private contextManager: ContextManager;
|
|
33
35
|
private checkpointManager: CheckpointManager;
|
|
36
|
+
private skillsManager: SkillsManager;
|
|
34
37
|
private history: ChatMessage[] = [];
|
|
35
38
|
private mode: 'PLAN' | 'BUILD' = 'BUILD';
|
|
36
39
|
private tools: Tool[] = [];
|
|
37
40
|
private mcpClients: McpClient[] = [];
|
|
38
41
|
private shell: PersistentShell;
|
|
39
42
|
private currentModelName: string = 'Unknown';
|
|
43
|
+
private activeSkill: string | null = null; // Track currently active skill for allowed-tools
|
|
40
44
|
|
|
41
45
|
constructor() {
|
|
42
46
|
this.configManager = new ConfigManager();
|
|
43
47
|
this.contextManager = new ContextManager();
|
|
44
48
|
this.checkpointManager = new CheckpointManager();
|
|
49
|
+
this.skillsManager = new SkillsManager();
|
|
45
50
|
this.shell = new PersistentShell();
|
|
51
|
+
|
|
52
|
+
// Create tools array without skill tools first
|
|
46
53
|
this.tools = [
|
|
47
54
|
new WriteFileTool(),
|
|
48
55
|
new ReadFileTool(),
|
|
@@ -64,6 +71,67 @@ export class ReplManager {
|
|
|
64
71
|
});
|
|
65
72
|
// Default to Ollama if not specified, assuming compatible endpoint
|
|
66
73
|
this.initializeClient();
|
|
74
|
+
|
|
75
|
+
// Initialize skills system after client is ready
|
|
76
|
+
this.initializeSkills();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Initialize the skills system
|
|
81
|
+
*/
|
|
82
|
+
private async initializeSkills() {
|
|
83
|
+
this.skillsManager.ensureDirectoriesExist();
|
|
84
|
+
await this.skillsManager.discoverSkills();
|
|
85
|
+
|
|
86
|
+
// Add skill tools to the tools list
|
|
87
|
+
// Pass callback to LoadSkillTool to track active skill
|
|
88
|
+
this.tools.push(
|
|
89
|
+
new LoadSkillTool(this.skillsManager, (skill) => {
|
|
90
|
+
this.activeSkill = skill ? skill.name : null;
|
|
91
|
+
}),
|
|
92
|
+
new ListSkillsTool(this.skillsManager),
|
|
93
|
+
new ReadSkillFileTool(this.skillsManager)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if a tool is allowed by the currently active skill
|
|
99
|
+
* Returns true if tool is allowed, false if it requires confirmation
|
|
100
|
+
*/
|
|
101
|
+
private isToolAllowedBySkill(toolName: string): boolean {
|
|
102
|
+
if (!this.activeSkill) {
|
|
103
|
+
// No active skill, all tools require confirmation as per normal flow
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const skill = this.skillsManager.getSkill(this.activeSkill);
|
|
108
|
+
if (!skill || !skill.allowedTools || skill.allowedTools.length === 0) {
|
|
109
|
+
// No skill or no allowed-tools restriction
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Map tool names to allowed tool names
|
|
114
|
+
const toolMapping: Record<string, string> = {
|
|
115
|
+
'write_file': 'Write',
|
|
116
|
+
'read_file': 'Read',
|
|
117
|
+
'edit_file': 'Edit',
|
|
118
|
+
'search_files': 'Grep',
|
|
119
|
+
'list_dir': 'ListDir',
|
|
120
|
+
'search_file': 'SearchFile',
|
|
121
|
+
'run_shell': 'RunShell',
|
|
122
|
+
'web_search': 'WebSearch',
|
|
123
|
+
'git_status': 'GitStatus',
|
|
124
|
+
'git_diff': 'GitDiff',
|
|
125
|
+
'git_commit': 'GitCommit',
|
|
126
|
+
'git_push': 'GitPush',
|
|
127
|
+
'git_pull': 'GitPull',
|
|
128
|
+
'load_skill': 'Read',
|
|
129
|
+
'list_skills': 'Read',
|
|
130
|
+
'read_skill_file': 'Read'
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const mappedToolName = toolMapping[toolName] || toolName;
|
|
134
|
+
return skill.allowedTools.includes(mappedToolName);
|
|
67
135
|
}
|
|
68
136
|
|
|
69
137
|
private initializeClient() {
|
|
@@ -99,29 +167,28 @@ export class ReplManager {
|
|
|
99
167
|
}
|
|
100
168
|
|
|
101
169
|
public async start() {
|
|
102
|
-
UIManager.
|
|
103
|
-
|
|
170
|
+
UIManager.renderDashboard({
|
|
171
|
+
model: this.currentModelName,
|
|
172
|
+
mode: this.mode,
|
|
173
|
+
cwd: process.cwd()
|
|
174
|
+
});
|
|
104
175
|
|
|
105
176
|
// Load History
|
|
106
177
|
let commandHistory: string[] = [];
|
|
107
178
|
if (fs.existsSync(HISTORY_FILE)) {
|
|
108
179
|
try {
|
|
109
|
-
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
110
|
-
// readline.history is [newest, ..., oldest]
|
|
111
|
-
// If I read from file where newest is at bottom (standard append), I need to reverse it.
|
|
112
|
-
// Let's assume standard file: line 1 (old), line 2 (new).
|
|
113
|
-
// So split -> reverse -> history.
|
|
180
|
+
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
114
181
|
} catch (e) { }
|
|
115
182
|
}
|
|
116
183
|
|
|
117
184
|
while (true) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
185
|
+
// Minimalist Separator
|
|
186
|
+
console.log(chalk.gray('────────────────────────────────────────────────────────────────────────────────'));
|
|
187
|
+
|
|
188
|
+
// Hint (Claude style puts it below, we put it above for standard terminal compatibility)
|
|
189
|
+
console.log(chalk.dim(' ? for shortcuts'));
|
|
121
190
|
|
|
122
|
-
const
|
|
123
|
-
const modelInfo = this.currentModelName ? ` (${this.currentModelName})` : '';
|
|
124
|
-
const promptText = `${modeLabel}${chalk.dim(modelInfo)} ${chalk.cyan('>')}`;
|
|
191
|
+
const promptText = `> `; // Clean prompt
|
|
125
192
|
|
|
126
193
|
// Use readline for basic input to support history
|
|
127
194
|
const answer = await new Promise<string>((resolve) => {
|
|
@@ -130,7 +197,7 @@ export class ReplManager {
|
|
|
130
197
|
output: process.stdout,
|
|
131
198
|
history: commandHistory,
|
|
132
199
|
historySize: 1000,
|
|
133
|
-
prompt: promptText
|
|
200
|
+
prompt: promptText
|
|
134
201
|
});
|
|
135
202
|
|
|
136
203
|
rl.prompt();
|
|
@@ -179,16 +246,16 @@ export class ReplManager {
|
|
|
179
246
|
console.log(' /help - Show this help message');
|
|
180
247
|
console.log(' /clear - Clear chat history');
|
|
181
248
|
console.log(' /exit - Exit the application');
|
|
249
|
+
console.log(' /update - Check for and install updates');
|
|
182
250
|
console.log(' /config - Configure settings');
|
|
183
251
|
console.log(' /add <file> - Add file to context');
|
|
184
252
|
console.log(' /drop <file> - Remove file from context');
|
|
185
253
|
console.log(' /plan - Switch to PLAN mode');
|
|
186
254
|
console.log(' /build - Switch to BUILD mode');
|
|
187
|
-
console.log(' /plan - Switch to PLAN mode');
|
|
188
|
-
console.log(' /build - Switch to BUILD mode');
|
|
189
255
|
console.log(' /model - Interactively select Provider & Model');
|
|
190
256
|
console.log(' /use <provider> [model] - Quick switch (legacy)');
|
|
191
257
|
console.log(' /mcp <cmd> - Manage MCP servers');
|
|
258
|
+
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
192
259
|
console.log(' /resume - Resume last session');
|
|
193
260
|
console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
|
|
194
261
|
console.log(' /search <query> - Search codebase');
|
|
@@ -258,6 +325,14 @@ export class ReplManager {
|
|
|
258
325
|
console.log(chalk.green('Session saved. Goodbye!'));
|
|
259
326
|
process.exit(0);
|
|
260
327
|
break;
|
|
328
|
+
case '/update':
|
|
329
|
+
const UpdateManager = require('../utils/UpdateManager').UpdateManager;
|
|
330
|
+
const updater = new UpdateManager();
|
|
331
|
+
await updater.checkAndPerformUpdate(true);
|
|
332
|
+
break;
|
|
333
|
+
case '/skills':
|
|
334
|
+
await this.handleSkillsCommand(args);
|
|
335
|
+
break;
|
|
261
336
|
default:
|
|
262
337
|
console.log(chalk.red(`Unknown command: ${command}`));
|
|
263
338
|
}
|
|
@@ -265,6 +340,7 @@ export class ReplManager {
|
|
|
265
340
|
|
|
266
341
|
private async handleChat(input: string) {
|
|
267
342
|
const context = this.contextManager.getContextString();
|
|
343
|
+
const skillsContext = this.skillsManager.getSkillsContext();
|
|
268
344
|
let fullInput = input;
|
|
269
345
|
|
|
270
346
|
let modeInstruction = '';
|
|
@@ -276,6 +352,11 @@ export class ReplManager {
|
|
|
276
352
|
|
|
277
353
|
fullInput = `${input}${modeInstruction}`;
|
|
278
354
|
|
|
355
|
+
// Add skills context if available
|
|
356
|
+
if (skillsContext) {
|
|
357
|
+
fullInput = `${skillsContext}\n\n${fullInput}`;
|
|
358
|
+
}
|
|
359
|
+
|
|
279
360
|
if (context) {
|
|
280
361
|
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
281
362
|
}
|
|
@@ -338,7 +419,8 @@ export class ReplManager {
|
|
|
338
419
|
console.log(chalk.dim(` [Action] ${toolName}(${displayArgs})`));
|
|
339
420
|
|
|
340
421
|
// Safety check for write_file
|
|
341
|
-
if
|
|
422
|
+
// Skip confirmation if tool is allowed by active skill
|
|
423
|
+
if (toolName === 'write_file' && !this.isToolAllowedBySkill('Write')) {
|
|
342
424
|
// Pause cancellation listener during user interaction
|
|
343
425
|
if (process.stdin.isTTY) {
|
|
344
426
|
process.stdin.removeListener('keypress', keyListener);
|
|
@@ -858,6 +940,100 @@ export class ReplManager {
|
|
|
858
940
|
}
|
|
859
941
|
}
|
|
860
942
|
|
|
943
|
+
private async handleSkillsCommand(args: string[]) {
|
|
944
|
+
const { SkillCreator, validateSkills } = await import('../skills/SkillCreator');
|
|
945
|
+
|
|
946
|
+
if (args.length < 1) {
|
|
947
|
+
// Show skills list by default
|
|
948
|
+
await this.handleSkillsCommand(['list']);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const action = args[0];
|
|
953
|
+
|
|
954
|
+
switch (action) {
|
|
955
|
+
case 'list':
|
|
956
|
+
await this.handleSkillsList();
|
|
957
|
+
break;
|
|
958
|
+
case 'show':
|
|
959
|
+
if (args.length < 2) {
|
|
960
|
+
console.log(chalk.red('Usage: /skills show <name>'));
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
await this.handleSkillsShow(args[1]);
|
|
964
|
+
break;
|
|
965
|
+
case 'create':
|
|
966
|
+
const creator = new SkillCreator(this.skillsManager);
|
|
967
|
+
await creator.run(args[1]);
|
|
968
|
+
// Re-discover skills after creation
|
|
969
|
+
await this.skillsManager.discoverSkills();
|
|
970
|
+
break;
|
|
971
|
+
case 'validate':
|
|
972
|
+
await validateSkills(this.skillsManager);
|
|
973
|
+
break;
|
|
974
|
+
default:
|
|
975
|
+
console.log(chalk.red(`Unknown skills action: ${action}`));
|
|
976
|
+
console.log(chalk.yellow('Available actions: list, show, create, validate'));
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private async handleSkillsList(): Promise<void> {
|
|
981
|
+
const skills = this.skillsManager.getAllSkills();
|
|
982
|
+
|
|
983
|
+
if (skills.length === 0) {
|
|
984
|
+
console.log(chalk.yellow('No skills available.'));
|
|
985
|
+
console.log(chalk.dim('Create skills with: /skills create'));
|
|
986
|
+
console.log(chalk.dim('Add skills to: ~/.mentis/skills/ or .mentis/skills/'));
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
console.log(chalk.cyan(`\nAvailable Skills (${skills.length}):\n`));
|
|
991
|
+
|
|
992
|
+
for (const skill of skills) {
|
|
993
|
+
const statusIcon = skill.isValid ? '✓' : '✗';
|
|
994
|
+
const typeLabel = skill.type === 'personal' ? 'Personal' : 'Project';
|
|
995
|
+
|
|
996
|
+
console.log(`${statusIcon} ${chalk.bold(skill.name)} (${typeLabel})`);
|
|
997
|
+
console.log(` ${skill.description}`);
|
|
998
|
+
|
|
999
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
1000
|
+
console.log(chalk.dim(` Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (!skill.isValid && skill.errors) {
|
|
1004
|
+
console.log(chalk.red(` Errors: ${skill.errors.join(', ')}`));
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
console.log('');
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
private async handleSkillsShow(name: string): Promise<void> {
|
|
1012
|
+
const skill = await this.skillsManager.loadFullSkill(name);
|
|
1013
|
+
|
|
1014
|
+
if (!skill) {
|
|
1015
|
+
console.log(chalk.red(`Skill "${name}" not found.`));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
console.log(chalk.cyan(`\n# ${skill.name}\n`));
|
|
1020
|
+
console.log(chalk.dim(`Type: ${skill.type}`));
|
|
1021
|
+
console.log(chalk.dim(`Path: ${skill.path}`));
|
|
1022
|
+
|
|
1023
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
1024
|
+
console.log(chalk.dim(`Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
console.log('');
|
|
1028
|
+
console.log(skill.content || 'No content available');
|
|
1029
|
+
|
|
1030
|
+
// List supporting files
|
|
1031
|
+
const files = this.skillsManager.listSkillFiles(name);
|
|
1032
|
+
if (files.length > 0) {
|
|
1033
|
+
console.log(chalk.dim(`\nSupporting files: ${files.join(', ')}`));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
861
1037
|
private estimateCost(input: number, output: number): number {
|
|
862
1038
|
const config = this.configManager.getConfig();
|
|
863
1039
|
const provider = config.defaultProvider;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoadSkillTool - Tool for model-invoked skill loading
|
|
3
|
+
* The model can call this tool when it determines a skill is relevant to the current task
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Tool } from '../tools/Tool';
|
|
7
|
+
import { SkillsManager, Skill } from './SkillsManager';
|
|
8
|
+
|
|
9
|
+
interface LoadSkillArgs {
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type SkillLoadedCallback = (skill: Skill | null) => void;
|
|
14
|
+
|
|
15
|
+
export class LoadSkillTool implements Tool {
|
|
16
|
+
name = 'load_skill';
|
|
17
|
+
description = 'Load the full content of a skill by name. Use this when you need detailed instructions from a skill. Available skills can be seen in the system prompt. Example: load_skill({ name: "commit-helper" })';
|
|
18
|
+
parameters = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
name: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'The name of the skill to load (e.g., "commit-helper", "pdf-processing")'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: ['name']
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
private skillsManager: SkillsManager;
|
|
30
|
+
private onSkillLoaded?: SkillLoadedCallback;
|
|
31
|
+
|
|
32
|
+
constructor(skillsManager: SkillsManager, onSkillLoaded?: SkillLoadedCallback) {
|
|
33
|
+
this.skillsManager = skillsManager;
|
|
34
|
+
this.onSkillLoaded = onSkillLoaded;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async execute(args: LoadSkillArgs): Promise<string> {
|
|
38
|
+
const { name } = args;
|
|
39
|
+
|
|
40
|
+
if (!name) {
|
|
41
|
+
return 'Error: Skill name is required';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const skill = this.skillsManager.getSkill(name);
|
|
45
|
+
if (!skill) {
|
|
46
|
+
const availableSkills = this.skillsManager.getAllSkills().map(s => s.name).join(', ');
|
|
47
|
+
return `Error: Skill "${name}" not found. Available skills: ${availableSkills || 'none'}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Load full skill content
|
|
51
|
+
const fullSkill = await this.skillsManager.loadFullSkill(name);
|
|
52
|
+
if (!fullSkill || !fullSkill.content) {
|
|
53
|
+
return `Error: Failed to load content for skill "${name}"`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Notify callback that skill was loaded
|
|
57
|
+
if (this.onSkillLoaded) {
|
|
58
|
+
this.onSkillLoaded(fullSkill);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Format response with skill content
|
|
62
|
+
let response = `# Loaded Skill: ${skill.name}\n\n`;
|
|
63
|
+
response += `**Type**: ${skill.type}\n`;
|
|
64
|
+
response += `**Description**: ${skill.description}\n`;
|
|
65
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
66
|
+
response += `**Allowed Tools**: ${skill.allowedTools.join(', ')}\n`;
|
|
67
|
+
}
|
|
68
|
+
response += `\n---\n\n`;
|
|
69
|
+
response += fullSkill.content;
|
|
70
|
+
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ListSkillsTool - Tool for listing available skills
|
|
77
|
+
*/
|
|
78
|
+
export class ListSkillsTool implements Tool {
|
|
79
|
+
name = 'list_skills';
|
|
80
|
+
description = 'List all available skills with their descriptions. Use this to see what skills are available.';
|
|
81
|
+
parameters = {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {},
|
|
84
|
+
required: []
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
private skillsManager: SkillsManager;
|
|
88
|
+
|
|
89
|
+
constructor(skillsManager: SkillsManager) {
|
|
90
|
+
this.skillsManager = skillsManager;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async execute(): Promise<string> {
|
|
94
|
+
const skills = this.skillsManager.getAllSkills();
|
|
95
|
+
|
|
96
|
+
if (skills.length === 0) {
|
|
97
|
+
return 'No skills available. Add skills to ~/.mentis/skills/ or .mentis/skills/';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let response = `# Available Skills (${skills.length})\n\n`;
|
|
101
|
+
|
|
102
|
+
for (const skill of skills) {
|
|
103
|
+
const statusIcon = skill.isValid ? '✓' : '✗';
|
|
104
|
+
response += `**${statusIcon} ${skill.name}** (${skill.type})\n`;
|
|
105
|
+
response += ` ${skill.description}\n`;
|
|
106
|
+
|
|
107
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
108
|
+
response += ` Allowed tools: ${skill.allowedTools.join(', ')}\n`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!skill.isValid && skill.errors) {
|
|
112
|
+
response += ` Errors: ${skill.errors.join(', ')}\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
response += '\n';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return response;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* ReadSkillFileTool - Tool for reading supporting files within a skill
|
|
124
|
+
* Used for progressive disclosure of skill resources
|
|
125
|
+
*/
|
|
126
|
+
export class ReadSkillFileTool implements Tool {
|
|
127
|
+
name = 'read_skill_file';
|
|
128
|
+
description = 'Read a supporting file from within a skill directory. Use this when a skill references additional files like [reference.md](reference.md). Example: read_skill_file({ skill: "pdf-processing", file: "reference.md" })';
|
|
129
|
+
parameters = {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
skill: {
|
|
133
|
+
type: 'string',
|
|
134
|
+
description: 'The name of the skill'
|
|
135
|
+
},
|
|
136
|
+
file: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
description: 'The filename within the skill directory (e.g., "reference.md", "examples.md")'
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
required: ['skill', 'file']
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
private skillsManager: SkillsManager;
|
|
145
|
+
|
|
146
|
+
constructor(skillsManager: SkillsManager) {
|
|
147
|
+
this.skillsManager = skillsManager;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async execute(args: { skill: string; file: string }): Promise<string> {
|
|
151
|
+
const { skill, file } = args;
|
|
152
|
+
|
|
153
|
+
if (!skill || !file) {
|
|
154
|
+
return 'Error: Both skill and file parameters are required';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const content = this.skillsManager.readSkillFile(skill, file);
|
|
158
|
+
if (content === null) {
|
|
159
|
+
const availableFiles = this.skillsManager.listSkillFiles(skill);
|
|
160
|
+
if (availableFiles.length === 0) {
|
|
161
|
+
return `Error: Skill "${skill}" has no supporting files`;
|
|
162
|
+
}
|
|
163
|
+
return `Error: File "${file}" not found in skill "${skill}". Available files: ${availableFiles.join(', ')}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return content;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill data structure for Agent Skills system
|
|
3
|
+
* Based on Claude Code's Agent Skills format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface SkillMetadata {
|
|
7
|
+
name: string; // Lowercase, numbers, hyphens only (max 64 chars)
|
|
8
|
+
description: string; // What it does + when to use it (max 1024 chars)
|
|
9
|
+
allowedTools?: string[]; // Optional tool restrictions
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Skill extends SkillMetadata {
|
|
13
|
+
path: string; // Path to SKILL.md
|
|
14
|
+
type: 'personal' | 'project' | 'plugin';
|
|
15
|
+
content?: string; // Loaded on demand (progressive disclosure)
|
|
16
|
+
directory: string; // Path to skill directory (for resolving supporting files)
|
|
17
|
+
isValid: boolean; // Whether skill passes validation
|
|
18
|
+
errors?: string[]; // Validation errors if any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SkillFrontmatter {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
'allowed-tools'?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validation result for a skill
|
|
29
|
+
*/
|
|
30
|
+
export interface SkillValidationResult {
|
|
31
|
+
isValid: boolean;
|
|
32
|
+
errors: string[];
|
|
33
|
+
warnings: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Skill context format for model injection
|
|
38
|
+
*/
|
|
39
|
+
export interface SkillContext {
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Options for skill discovery
|
|
46
|
+
*/
|
|
47
|
+
export interface SkillDiscoveryOptions {
|
|
48
|
+
includePersonal?: boolean;
|
|
49
|
+
includeProject?: boolean;
|
|
50
|
+
includePlugin?: boolean;
|
|
51
|
+
}
|