@indiccoder/mentis-cli 1.0.8 ā 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/repl/ReplManager.js +142 -3
- 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/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/repl/ReplManager.ts +174 -3
- 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/dist/repl/ReplManager.js
CHANGED
|
@@ -52,6 +52,8 @@ const WebSearchTool_1 = require("../tools/WebSearchTool");
|
|
|
52
52
|
const GitTools_1 = require("../tools/GitTools");
|
|
53
53
|
const McpClient_1 = require("../mcp/McpClient");
|
|
54
54
|
const CheckpointManager_1 = require("../checkpoint/CheckpointManager");
|
|
55
|
+
const SkillsManager_1 = require("../skills/SkillsManager");
|
|
56
|
+
const LoadSkillTool_1 = require("../skills/LoadSkillTool");
|
|
55
57
|
const readline = __importStar(require("readline"));
|
|
56
58
|
const fs = __importStar(require("fs"));
|
|
57
59
|
const path = __importStar(require("path"));
|
|
@@ -66,10 +68,13 @@ class ReplManager {
|
|
|
66
68
|
this.tools = [];
|
|
67
69
|
this.mcpClients = [];
|
|
68
70
|
this.currentModelName = 'Unknown';
|
|
71
|
+
this.activeSkill = null; // Track currently active skill for allowed-tools
|
|
69
72
|
this.configManager = new ConfigManager_1.ConfigManager();
|
|
70
73
|
this.contextManager = new ContextManager_1.ContextManager();
|
|
71
74
|
this.checkpointManager = new CheckpointManager_1.CheckpointManager();
|
|
75
|
+
this.skillsManager = new SkillsManager_1.SkillsManager();
|
|
72
76
|
this.shell = new PersistentShell_1.PersistentShell();
|
|
77
|
+
// Create tools array without skill tools first
|
|
73
78
|
this.tools = [
|
|
74
79
|
new FileTools_1.WriteFileTool(),
|
|
75
80
|
new FileTools_1.ReadFileTool(),
|
|
@@ -90,6 +95,56 @@ class ReplManager {
|
|
|
90
95
|
});
|
|
91
96
|
// Default to Ollama if not specified, assuming compatible endpoint
|
|
92
97
|
this.initializeClient();
|
|
98
|
+
// Initialize skills system after client is ready
|
|
99
|
+
this.initializeSkills();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Initialize the skills system
|
|
103
|
+
*/
|
|
104
|
+
async initializeSkills() {
|
|
105
|
+
this.skillsManager.ensureDirectoriesExist();
|
|
106
|
+
await this.skillsManager.discoverSkills();
|
|
107
|
+
// Add skill tools to the tools list
|
|
108
|
+
// Pass callback to LoadSkillTool to track active skill
|
|
109
|
+
this.tools.push(new LoadSkillTool_1.LoadSkillTool(this.skillsManager, (skill) => {
|
|
110
|
+
this.activeSkill = skill ? skill.name : null;
|
|
111
|
+
}), new LoadSkillTool_1.ListSkillsTool(this.skillsManager), new LoadSkillTool_1.ReadSkillFileTool(this.skillsManager));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if a tool is allowed by the currently active skill
|
|
115
|
+
* Returns true if tool is allowed, false if it requires confirmation
|
|
116
|
+
*/
|
|
117
|
+
isToolAllowedBySkill(toolName) {
|
|
118
|
+
if (!this.activeSkill) {
|
|
119
|
+
// No active skill, all tools require confirmation as per normal flow
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const skill = this.skillsManager.getSkill(this.activeSkill);
|
|
123
|
+
if (!skill || !skill.allowedTools || skill.allowedTools.length === 0) {
|
|
124
|
+
// No skill or no allowed-tools restriction
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
// Map tool names to allowed tool names
|
|
128
|
+
const toolMapping = {
|
|
129
|
+
'write_file': 'Write',
|
|
130
|
+
'read_file': 'Read',
|
|
131
|
+
'edit_file': 'Edit',
|
|
132
|
+
'search_files': 'Grep',
|
|
133
|
+
'list_dir': 'ListDir',
|
|
134
|
+
'search_file': 'SearchFile',
|
|
135
|
+
'run_shell': 'RunShell',
|
|
136
|
+
'web_search': 'WebSearch',
|
|
137
|
+
'git_status': 'GitStatus',
|
|
138
|
+
'git_diff': 'GitDiff',
|
|
139
|
+
'git_commit': 'GitCommit',
|
|
140
|
+
'git_push': 'GitPush',
|
|
141
|
+
'git_pull': 'GitPull',
|
|
142
|
+
'load_skill': 'Read',
|
|
143
|
+
'list_skills': 'Read',
|
|
144
|
+
'read_skill_file': 'Read'
|
|
145
|
+
};
|
|
146
|
+
const mappedToolName = toolMapping[toolName] || toolName;
|
|
147
|
+
return skill.allowedTools.includes(mappedToolName);
|
|
93
148
|
}
|
|
94
149
|
initializeClient() {
|
|
95
150
|
const config = this.configManager.getConfig();
|
|
@@ -197,11 +252,10 @@ class ReplManager {
|
|
|
197
252
|
console.log(' /drop <file> - Remove file from context');
|
|
198
253
|
console.log(' /plan - Switch to PLAN mode');
|
|
199
254
|
console.log(' /build - Switch to BUILD mode');
|
|
200
|
-
console.log(' /plan - Switch to PLAN mode');
|
|
201
|
-
console.log(' /build - Switch to BUILD mode');
|
|
202
255
|
console.log(' /model - Interactively select Provider & Model');
|
|
203
256
|
console.log(' /use <provider> [model] - Quick switch (legacy)');
|
|
204
257
|
console.log(' /mcp <cmd> - Manage MCP servers');
|
|
258
|
+
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
205
259
|
console.log(' /resume - Resume last session');
|
|
206
260
|
console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
|
|
207
261
|
console.log(' /search <query> - Search codebase');
|
|
@@ -278,12 +332,16 @@ class ReplManager {
|
|
|
278
332
|
const updater = new UpdateManager();
|
|
279
333
|
await updater.checkAndPerformUpdate(true);
|
|
280
334
|
break;
|
|
335
|
+
case '/skills':
|
|
336
|
+
await this.handleSkillsCommand(args);
|
|
337
|
+
break;
|
|
281
338
|
default:
|
|
282
339
|
console.log(chalk_1.default.red(`Unknown command: ${command}`));
|
|
283
340
|
}
|
|
284
341
|
}
|
|
285
342
|
async handleChat(input) {
|
|
286
343
|
const context = this.contextManager.getContextString();
|
|
344
|
+
const skillsContext = this.skillsManager.getSkillsContext();
|
|
287
345
|
let fullInput = input;
|
|
288
346
|
let modeInstruction = '';
|
|
289
347
|
if (this.mode === 'PLAN') {
|
|
@@ -293,6 +351,10 @@ class ReplManager {
|
|
|
293
351
|
modeInstruction = '\n[SYSTEM: You are in BUILD mode. Focus on implementing working code that solves the user request efficiently.]';
|
|
294
352
|
}
|
|
295
353
|
fullInput = `${input}${modeInstruction}`;
|
|
354
|
+
// Add skills context if available
|
|
355
|
+
if (skillsContext) {
|
|
356
|
+
fullInput = `${skillsContext}\n\n${fullInput}`;
|
|
357
|
+
}
|
|
296
358
|
if (context) {
|
|
297
359
|
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
298
360
|
}
|
|
@@ -345,7 +407,8 @@ class ReplManager {
|
|
|
345
407
|
}
|
|
346
408
|
console.log(chalk_1.default.dim(` [Action] ${toolName}(${displayArgs})`));
|
|
347
409
|
// Safety check for write_file
|
|
348
|
-
if
|
|
410
|
+
// Skip confirmation if tool is allowed by active skill
|
|
411
|
+
if (toolName === 'write_file' && !this.isToolAllowedBySkill('Write')) {
|
|
349
412
|
// Pause cancellation listener during user interaction
|
|
350
413
|
if (process.stdin.isTTY) {
|
|
351
414
|
process.stdin.removeListener('keypress', keyListener);
|
|
@@ -827,6 +890,82 @@ class ReplManager {
|
|
|
827
890
|
console.log(lastMsg.content);
|
|
828
891
|
}
|
|
829
892
|
}
|
|
893
|
+
async handleSkillsCommand(args) {
|
|
894
|
+
const { SkillCreator, validateSkills } = await Promise.resolve().then(() => __importStar(require('../skills/SkillCreator')));
|
|
895
|
+
if (args.length < 1) {
|
|
896
|
+
// Show skills list by default
|
|
897
|
+
await this.handleSkillsCommand(['list']);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
const action = args[0];
|
|
901
|
+
switch (action) {
|
|
902
|
+
case 'list':
|
|
903
|
+
await this.handleSkillsList();
|
|
904
|
+
break;
|
|
905
|
+
case 'show':
|
|
906
|
+
if (args.length < 2) {
|
|
907
|
+
console.log(chalk_1.default.red('Usage: /skills show <name>'));
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
await this.handleSkillsShow(args[1]);
|
|
911
|
+
break;
|
|
912
|
+
case 'create':
|
|
913
|
+
const creator = new SkillCreator(this.skillsManager);
|
|
914
|
+
await creator.run(args[1]);
|
|
915
|
+
// Re-discover skills after creation
|
|
916
|
+
await this.skillsManager.discoverSkills();
|
|
917
|
+
break;
|
|
918
|
+
case 'validate':
|
|
919
|
+
await validateSkills(this.skillsManager);
|
|
920
|
+
break;
|
|
921
|
+
default:
|
|
922
|
+
console.log(chalk_1.default.red(`Unknown skills action: ${action}`));
|
|
923
|
+
console.log(chalk_1.default.yellow('Available actions: list, show, create, validate'));
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
async handleSkillsList() {
|
|
927
|
+
const skills = this.skillsManager.getAllSkills();
|
|
928
|
+
if (skills.length === 0) {
|
|
929
|
+
console.log(chalk_1.default.yellow('No skills available.'));
|
|
930
|
+
console.log(chalk_1.default.dim('Create skills with: /skills create'));
|
|
931
|
+
console.log(chalk_1.default.dim('Add skills to: ~/.mentis/skills/ or .mentis/skills/'));
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
console.log(chalk_1.default.cyan(`\nAvailable Skills (${skills.length}):\n`));
|
|
935
|
+
for (const skill of skills) {
|
|
936
|
+
const statusIcon = skill.isValid ? 'ā' : 'ā';
|
|
937
|
+
const typeLabel = skill.type === 'personal' ? 'Personal' : 'Project';
|
|
938
|
+
console.log(`${statusIcon} ${chalk_1.default.bold(skill.name)} (${typeLabel})`);
|
|
939
|
+
console.log(` ${skill.description}`);
|
|
940
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
941
|
+
console.log(chalk_1.default.dim(` Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
942
|
+
}
|
|
943
|
+
if (!skill.isValid && skill.errors) {
|
|
944
|
+
console.log(chalk_1.default.red(` Errors: ${skill.errors.join(', ')}`));
|
|
945
|
+
}
|
|
946
|
+
console.log('');
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async handleSkillsShow(name) {
|
|
950
|
+
const skill = await this.skillsManager.loadFullSkill(name);
|
|
951
|
+
if (!skill) {
|
|
952
|
+
console.log(chalk_1.default.red(`Skill "${name}" not found.`));
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
console.log(chalk_1.default.cyan(`\n# ${skill.name}\n`));
|
|
956
|
+
console.log(chalk_1.default.dim(`Type: ${skill.type}`));
|
|
957
|
+
console.log(chalk_1.default.dim(`Path: ${skill.path}`));
|
|
958
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
959
|
+
console.log(chalk_1.default.dim(`Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
960
|
+
}
|
|
961
|
+
console.log('');
|
|
962
|
+
console.log(skill.content || 'No content available');
|
|
963
|
+
// List supporting files
|
|
964
|
+
const files = this.skillsManager.listSkillFiles(name);
|
|
965
|
+
if (files.length > 0) {
|
|
966
|
+
console.log(chalk_1.default.dim(`\nSupporting files: ${files.join(', ')}`));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
830
969
|
estimateCost(input, output) {
|
|
831
970
|
const config = this.configManager.getConfig();
|
|
832
971
|
const provider = config.defaultProvider;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LoadSkillTool - Tool for model-invoked skill loading
|
|
4
|
+
* The model can call this tool when it determines a skill is relevant to the current task
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ReadSkillFileTool = exports.ListSkillsTool = exports.LoadSkillTool = void 0;
|
|
8
|
+
class LoadSkillTool {
|
|
9
|
+
constructor(skillsManager, onSkillLoaded) {
|
|
10
|
+
this.name = 'load_skill';
|
|
11
|
+
this.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" })';
|
|
12
|
+
this.parameters = {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
name: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'The name of the skill to load (e.g., "commit-helper", "pdf-processing")'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
required: ['name']
|
|
21
|
+
};
|
|
22
|
+
this.skillsManager = skillsManager;
|
|
23
|
+
this.onSkillLoaded = onSkillLoaded;
|
|
24
|
+
}
|
|
25
|
+
async execute(args) {
|
|
26
|
+
const { name } = args;
|
|
27
|
+
if (!name) {
|
|
28
|
+
return 'Error: Skill name is required';
|
|
29
|
+
}
|
|
30
|
+
const skill = this.skillsManager.getSkill(name);
|
|
31
|
+
if (!skill) {
|
|
32
|
+
const availableSkills = this.skillsManager.getAllSkills().map(s => s.name).join(', ');
|
|
33
|
+
return `Error: Skill "${name}" not found. Available skills: ${availableSkills || 'none'}`;
|
|
34
|
+
}
|
|
35
|
+
// Load full skill content
|
|
36
|
+
const fullSkill = await this.skillsManager.loadFullSkill(name);
|
|
37
|
+
if (!fullSkill || !fullSkill.content) {
|
|
38
|
+
return `Error: Failed to load content for skill "${name}"`;
|
|
39
|
+
}
|
|
40
|
+
// Notify callback that skill was loaded
|
|
41
|
+
if (this.onSkillLoaded) {
|
|
42
|
+
this.onSkillLoaded(fullSkill);
|
|
43
|
+
}
|
|
44
|
+
// Format response with skill content
|
|
45
|
+
let response = `# Loaded Skill: ${skill.name}\n\n`;
|
|
46
|
+
response += `**Type**: ${skill.type}\n`;
|
|
47
|
+
response += `**Description**: ${skill.description}\n`;
|
|
48
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
49
|
+
response += `**Allowed Tools**: ${skill.allowedTools.join(', ')}\n`;
|
|
50
|
+
}
|
|
51
|
+
response += `\n---\n\n`;
|
|
52
|
+
response += fullSkill.content;
|
|
53
|
+
return response;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.LoadSkillTool = LoadSkillTool;
|
|
57
|
+
/**
|
|
58
|
+
* ListSkillsTool - Tool for listing available skills
|
|
59
|
+
*/
|
|
60
|
+
class ListSkillsTool {
|
|
61
|
+
constructor(skillsManager) {
|
|
62
|
+
this.name = 'list_skills';
|
|
63
|
+
this.description = 'List all available skills with their descriptions. Use this to see what skills are available.';
|
|
64
|
+
this.parameters = {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {},
|
|
67
|
+
required: []
|
|
68
|
+
};
|
|
69
|
+
this.skillsManager = skillsManager;
|
|
70
|
+
}
|
|
71
|
+
async execute() {
|
|
72
|
+
const skills = this.skillsManager.getAllSkills();
|
|
73
|
+
if (skills.length === 0) {
|
|
74
|
+
return 'No skills available. Add skills to ~/.mentis/skills/ or .mentis/skills/';
|
|
75
|
+
}
|
|
76
|
+
let response = `# Available Skills (${skills.length})\n\n`;
|
|
77
|
+
for (const skill of skills) {
|
|
78
|
+
const statusIcon = skill.isValid ? 'ā' : 'ā';
|
|
79
|
+
response += `**${statusIcon} ${skill.name}** (${skill.type})\n`;
|
|
80
|
+
response += ` ${skill.description}\n`;
|
|
81
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
82
|
+
response += ` Allowed tools: ${skill.allowedTools.join(', ')}\n`;
|
|
83
|
+
}
|
|
84
|
+
if (!skill.isValid && skill.errors) {
|
|
85
|
+
response += ` Errors: ${skill.errors.join(', ')}\n`;
|
|
86
|
+
}
|
|
87
|
+
response += '\n';
|
|
88
|
+
}
|
|
89
|
+
return response;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.ListSkillsTool = ListSkillsTool;
|
|
93
|
+
/**
|
|
94
|
+
* ReadSkillFileTool - Tool for reading supporting files within a skill
|
|
95
|
+
* Used for progressive disclosure of skill resources
|
|
96
|
+
*/
|
|
97
|
+
class ReadSkillFileTool {
|
|
98
|
+
constructor(skillsManager) {
|
|
99
|
+
this.name = 'read_skill_file';
|
|
100
|
+
this.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" })';
|
|
101
|
+
this.parameters = {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {
|
|
104
|
+
skill: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'The name of the skill'
|
|
107
|
+
},
|
|
108
|
+
file: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
description: 'The filename within the skill directory (e.g., "reference.md", "examples.md")'
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
required: ['skill', 'file']
|
|
114
|
+
};
|
|
115
|
+
this.skillsManager = skillsManager;
|
|
116
|
+
}
|
|
117
|
+
async execute(args) {
|
|
118
|
+
const { skill, file } = args;
|
|
119
|
+
if (!skill || !file) {
|
|
120
|
+
return 'Error: Both skill and file parameters are required';
|
|
121
|
+
}
|
|
122
|
+
const content = this.skillsManager.readSkillFile(skill, file);
|
|
123
|
+
if (content === null) {
|
|
124
|
+
const availableFiles = this.skillsManager.listSkillFiles(skill);
|
|
125
|
+
if (availableFiles.length === 0) {
|
|
126
|
+
return `Error: Skill "${skill}" has no supporting files`;
|
|
127
|
+
}
|
|
128
|
+
return `Error: File "${file}" not found in skill "${skill}". Available files: ${availableFiles.join(', ')}`;
|
|
129
|
+
}
|
|
130
|
+
return content;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.ReadSkillFileTool = ReadSkillFileTool;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SkillCreator - Interactive wizard for creating new skills
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.SkillCreator = void 0;
|
|
43
|
+
exports.validateSkills = validateSkills;
|
|
44
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const SkillsManager_1 = require("./SkillsManager");
|
|
49
|
+
class SkillCreator {
|
|
50
|
+
constructor(skillsManager) {
|
|
51
|
+
this.skillsManager = skillsManager || new SkillsManager_1.SkillsManager();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run the interactive skill creation wizard
|
|
55
|
+
*/
|
|
56
|
+
async run(name) {
|
|
57
|
+
console.log('\nš Create a new Skill\n');
|
|
58
|
+
let skillName;
|
|
59
|
+
let skillType;
|
|
60
|
+
let description;
|
|
61
|
+
let allowedTools;
|
|
62
|
+
// Step 1: Skill Name
|
|
63
|
+
if (name) {
|
|
64
|
+
skillName = name;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const { name: inputName } = await inquirer_1.default.prompt([
|
|
68
|
+
{
|
|
69
|
+
type: 'input',
|
|
70
|
+
name: 'name',
|
|
71
|
+
message: 'Skill name (lowercase, numbers, hyphens only):',
|
|
72
|
+
validate: (input) => {
|
|
73
|
+
if (!input)
|
|
74
|
+
return 'Name is required';
|
|
75
|
+
if (!/^[a-z0-9-]+$/.test(input)) {
|
|
76
|
+
return 'Name must contain only lowercase letters, numbers, and hyphens';
|
|
77
|
+
}
|
|
78
|
+
if (input.length > 64)
|
|
79
|
+
return 'Name must be 64 characters or less';
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
skillName = inputName;
|
|
85
|
+
}
|
|
86
|
+
// Step 2: Skill Type
|
|
87
|
+
const { type } = await inquirer_1.default.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'list',
|
|
90
|
+
name: 'type',
|
|
91
|
+
message: 'Skill type:',
|
|
92
|
+
choices: [
|
|
93
|
+
{ name: 'Personal (available in all projects)', value: 'personal' },
|
|
94
|
+
{ name: 'Project (shared with team via git)', value: 'project' }
|
|
95
|
+
],
|
|
96
|
+
default: 'personal'
|
|
97
|
+
}
|
|
98
|
+
]);
|
|
99
|
+
skillType = type;
|
|
100
|
+
// Step 3: Description
|
|
101
|
+
const { desc } = await inquirer_1.default.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: 'input',
|
|
104
|
+
name: 'desc',
|
|
105
|
+
message: 'Description (what it does + when to use it):',
|
|
106
|
+
validate: (input) => {
|
|
107
|
+
if (!input)
|
|
108
|
+
return 'Description is required';
|
|
109
|
+
if (input.length > 1024)
|
|
110
|
+
return 'Description must be 1024 characters or less';
|
|
111
|
+
if (!input.toLowerCase().includes('use when') && !input.toLowerCase().includes('use for')) {
|
|
112
|
+
return 'Tip: Include when to use this skill (e.g., "Use when...")';
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
]);
|
|
118
|
+
description = desc;
|
|
119
|
+
// Step 4: Allowed Tools (optional)
|
|
120
|
+
const { useAllowedTools } = await inquirer_1.default.prompt([
|
|
121
|
+
{
|
|
122
|
+
type: 'confirm',
|
|
123
|
+
name: 'useAllowedTools',
|
|
124
|
+
message: 'Restrict which tools this skill can use?',
|
|
125
|
+
default: false
|
|
126
|
+
}
|
|
127
|
+
]);
|
|
128
|
+
if (useAllowedTools) {
|
|
129
|
+
const { tools } = await inquirer_1.default.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: 'checkbox',
|
|
132
|
+
name: 'tools',
|
|
133
|
+
message: 'Select allowed tools:',
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: 'Read (read_file)', value: 'Read' },
|
|
136
|
+
{ name: 'Write (write_file)', value: 'Write' },
|
|
137
|
+
{ name: 'Edit (edit_file)', value: 'Edit' },
|
|
138
|
+
{ name: 'Grep (search files)', value: 'Grep' },
|
|
139
|
+
{ name: 'Glob (find files)', value: 'Glob' },
|
|
140
|
+
{ name: 'ListDir (list directory)', value: 'ListDir' },
|
|
141
|
+
{ name: 'SearchFile (search in files)', value: 'SearchFile' },
|
|
142
|
+
{ name: 'RunShell (run shell command)', value: 'RunShell' },
|
|
143
|
+
{ name: 'WebSearch (web search)', value: 'WebSearch' },
|
|
144
|
+
{ name: 'GitStatus', value: 'GitStatus' },
|
|
145
|
+
{ name: 'GitDiff', value: 'GitDiff' },
|
|
146
|
+
{ name: 'GitCommit', value: 'GitCommit' },
|
|
147
|
+
{ name: 'GitPush', value: 'GitPush' },
|
|
148
|
+
{ name: 'GitPull', value: 'GitPull' }
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
]);
|
|
152
|
+
allowedTools = tools.length > 0 ? tools : undefined;
|
|
153
|
+
}
|
|
154
|
+
// Step 5: Create the skill
|
|
155
|
+
return this.createSkill(skillName, skillType, description, allowedTools);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create the skill file and directory
|
|
159
|
+
*/
|
|
160
|
+
async createSkill(name, type, description, allowedTools) {
|
|
161
|
+
const baseDir = type === 'personal'
|
|
162
|
+
? path.join(os.homedir(), '.mentis', 'skills')
|
|
163
|
+
: path.join(process.cwd(), '.mentis', 'skills');
|
|
164
|
+
const skillDir = path.join(baseDir, name);
|
|
165
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
166
|
+
// Check if skill already exists
|
|
167
|
+
if (fs.existsSync(skillFile)) {
|
|
168
|
+
const { overwrite } = await inquirer_1.default.prompt([
|
|
169
|
+
{
|
|
170
|
+
type: 'confirm',
|
|
171
|
+
name: 'overwrite',
|
|
172
|
+
message: `Skill "${name}" already exists. Overwrite?`,
|
|
173
|
+
default: false
|
|
174
|
+
}
|
|
175
|
+
]);
|
|
176
|
+
if (!overwrite) {
|
|
177
|
+
console.log('Cancelled.');
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Create directory
|
|
182
|
+
if (!fs.existsSync(skillDir)) {
|
|
183
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
// Generate SKILL.md content
|
|
186
|
+
let content = `---\nname: ${name}\ndescription: ${description}\n`;
|
|
187
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
188
|
+
content += `allowed-tools: [${allowedTools.map(t => `"${t}"`).join(', ')}]\n`;
|
|
189
|
+
}
|
|
190
|
+
content += `---\n\n# ${this.formatTitle(name)}\n\n`;
|
|
191
|
+
content += `## Overview\n\n`;
|
|
192
|
+
content += `This skill provides...\n\n`;
|
|
193
|
+
content += `## Instructions\n\n`;
|
|
194
|
+
content += `### Step 1: ...\n\n`;
|
|
195
|
+
content += `### Step 2: ...\n\n`;
|
|
196
|
+
content += `## Examples\n\n`;
|
|
197
|
+
content += `\`\`\`\n`;
|
|
198
|
+
content += `// Example usage\n`;
|
|
199
|
+
content += `\`\`\`\n`;
|
|
200
|
+
// Write SKILL.md
|
|
201
|
+
fs.writeFileSync(skillFile, content, 'utf-8');
|
|
202
|
+
console.log(`\nā Skill created at: ${skillFile}`);
|
|
203
|
+
console.log(`\nNext steps:`);
|
|
204
|
+
console.log(` 1. Edit ${skillFile} to add instructions`);
|
|
205
|
+
console.log(` 2. Add supporting files (reference.md, examples.md, scripts/) as needed`);
|
|
206
|
+
console.log(` 3. Restart Mentis to load the new skill`);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Format skill name to title case
|
|
211
|
+
*/
|
|
212
|
+
formatTitle(name) {
|
|
213
|
+
return name
|
|
214
|
+
.split('-')
|
|
215
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
216
|
+
.join(' ');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
exports.SkillCreator = SkillCreator;
|
|
220
|
+
/**
|
|
221
|
+
* Validate skills and show results
|
|
222
|
+
*/
|
|
223
|
+
async function validateSkills(skillsManager) {
|
|
224
|
+
const results = skillsManager.validateAllSkills();
|
|
225
|
+
console.log('\nš Skill Validation Results\n');
|
|
226
|
+
let hasErrors = false;
|
|
227
|
+
let hasWarnings = false;
|
|
228
|
+
for (const [name, result] of results) {
|
|
229
|
+
if (result.isValid) {
|
|
230
|
+
console.log(`ā ${name}`);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
console.log(`ā ${name}`);
|
|
234
|
+
hasErrors = true;
|
|
235
|
+
}
|
|
236
|
+
if (result.errors.length > 0) {
|
|
237
|
+
result.errors.forEach(err => console.log(` ERROR: ${err}`));
|
|
238
|
+
}
|
|
239
|
+
if (result.warnings.length > 0) {
|
|
240
|
+
hasWarnings = true;
|
|
241
|
+
result.warnings.forEach(warn => console.log(` WARNING: ${warn}`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!hasErrors && !hasWarnings) {
|
|
245
|
+
console.log('\nā All skills are valid!');
|
|
246
|
+
}
|
|
247
|
+
}
|