@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/dist/index.js
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const ReplManager_1 = require("./repl/ReplManager");
|
|
5
5
|
async function main() {
|
|
6
|
+
if (process.argv.includes('update')) {
|
|
7
|
+
const { UpdateManager } = require('./utils/UpdateManager');
|
|
8
|
+
const updater = new UpdateManager();
|
|
9
|
+
await updater.checkAndPerformUpdate(true);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
6
12
|
const repl = new ReplManager_1.ReplManager();
|
|
7
13
|
await repl.start();
|
|
8
14
|
}
|
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();
|
|
@@ -123,27 +178,25 @@ class ReplManager {
|
|
|
123
178
|
// console.log(chalk.dim(`Initialized ${provider} client with model ${model}`));
|
|
124
179
|
}
|
|
125
180
|
async start() {
|
|
126
|
-
UIManager_1.UIManager.
|
|
127
|
-
|
|
181
|
+
UIManager_1.UIManager.renderDashboard({
|
|
182
|
+
model: this.currentModelName,
|
|
183
|
+
mode: this.mode,
|
|
184
|
+
cwd: process.cwd()
|
|
185
|
+
});
|
|
128
186
|
// Load History
|
|
129
187
|
let commandHistory = [];
|
|
130
188
|
if (fs.existsSync(HISTORY_FILE)) {
|
|
131
189
|
try {
|
|
132
|
-
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
133
|
-
// readline.history is [newest, ..., oldest]
|
|
134
|
-
// If I read from file where newest is at bottom (standard append), I need to reverse it.
|
|
135
|
-
// Let's assume standard file: line 1 (old), line 2 (new).
|
|
136
|
-
// So split -> reverse -> history.
|
|
190
|
+
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
137
191
|
}
|
|
138
192
|
catch (e) { }
|
|
139
193
|
}
|
|
140
194
|
while (true) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
const promptText = `${modeLabel}${chalk_1.default.dim(modelInfo)} ${chalk_1.default.cyan('>')}`;
|
|
195
|
+
// Minimalist Separator
|
|
196
|
+
console.log(chalk_1.default.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
197
|
+
// Hint (Claude style puts it below, we put it above for standard terminal compatibility)
|
|
198
|
+
console.log(chalk_1.default.dim(' ? for shortcuts'));
|
|
199
|
+
const promptText = `> `; // Clean prompt
|
|
147
200
|
// Use readline for basic input to support history
|
|
148
201
|
const answer = await new Promise((resolve) => {
|
|
149
202
|
const rl = readline.createInterface({
|
|
@@ -151,7 +204,7 @@ class ReplManager {
|
|
|
151
204
|
output: process.stdout,
|
|
152
205
|
history: commandHistory,
|
|
153
206
|
historySize: 1000,
|
|
154
|
-
prompt: promptText
|
|
207
|
+
prompt: promptText
|
|
155
208
|
});
|
|
156
209
|
rl.prompt();
|
|
157
210
|
rl.on('line', (line) => {
|
|
@@ -193,16 +246,16 @@ class ReplManager {
|
|
|
193
246
|
console.log(' /help - Show this help message');
|
|
194
247
|
console.log(' /clear - Clear chat history');
|
|
195
248
|
console.log(' /exit - Exit the application');
|
|
249
|
+
console.log(' /update - Check for and install updates');
|
|
196
250
|
console.log(' /config - Configure settings');
|
|
197
251
|
console.log(' /add <file> - Add file to context');
|
|
198
252
|
console.log(' /drop <file> - Remove file from context');
|
|
199
253
|
console.log(' /plan - Switch to PLAN mode');
|
|
200
254
|
console.log(' /build - Switch to BUILD mode');
|
|
201
|
-
console.log(' /plan - Switch to PLAN mode');
|
|
202
|
-
console.log(' /build - Switch to BUILD mode');
|
|
203
255
|
console.log(' /model - Interactively select Provider & Model');
|
|
204
256
|
console.log(' /use <provider> [model] - Quick switch (legacy)');
|
|
205
257
|
console.log(' /mcp <cmd> - Manage MCP servers');
|
|
258
|
+
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
206
259
|
console.log(' /resume - Resume last session');
|
|
207
260
|
console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
|
|
208
261
|
console.log(' /search <query> - Search codebase');
|
|
@@ -274,12 +327,21 @@ class ReplManager {
|
|
|
274
327
|
console.log(chalk_1.default.green('Session saved. Goodbye!'));
|
|
275
328
|
process.exit(0);
|
|
276
329
|
break;
|
|
330
|
+
case '/update':
|
|
331
|
+
const UpdateManager = require('../utils/UpdateManager').UpdateManager;
|
|
332
|
+
const updater = new UpdateManager();
|
|
333
|
+
await updater.checkAndPerformUpdate(true);
|
|
334
|
+
break;
|
|
335
|
+
case '/skills':
|
|
336
|
+
await this.handleSkillsCommand(args);
|
|
337
|
+
break;
|
|
277
338
|
default:
|
|
278
339
|
console.log(chalk_1.default.red(`Unknown command: ${command}`));
|
|
279
340
|
}
|
|
280
341
|
}
|
|
281
342
|
async handleChat(input) {
|
|
282
343
|
const context = this.contextManager.getContextString();
|
|
344
|
+
const skillsContext = this.skillsManager.getSkillsContext();
|
|
283
345
|
let fullInput = input;
|
|
284
346
|
let modeInstruction = '';
|
|
285
347
|
if (this.mode === 'PLAN') {
|
|
@@ -289,6 +351,10 @@ class ReplManager {
|
|
|
289
351
|
modeInstruction = '\n[SYSTEM: You are in BUILD mode. Focus on implementing working code that solves the user request efficiently.]';
|
|
290
352
|
}
|
|
291
353
|
fullInput = `${input}${modeInstruction}`;
|
|
354
|
+
// Add skills context if available
|
|
355
|
+
if (skillsContext) {
|
|
356
|
+
fullInput = `${skillsContext}\n\n${fullInput}`;
|
|
357
|
+
}
|
|
292
358
|
if (context) {
|
|
293
359
|
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
294
360
|
}
|
|
@@ -341,7 +407,8 @@ class ReplManager {
|
|
|
341
407
|
}
|
|
342
408
|
console.log(chalk_1.default.dim(` [Action] ${toolName}(${displayArgs})`));
|
|
343
409
|
// Safety check for write_file
|
|
344
|
-
if
|
|
410
|
+
// Skip confirmation if tool is allowed by active skill
|
|
411
|
+
if (toolName === 'write_file' && !this.isToolAllowedBySkill('Write')) {
|
|
345
412
|
// Pause cancellation listener during user interaction
|
|
346
413
|
if (process.stdin.isTTY) {
|
|
347
414
|
process.stdin.removeListener('keypress', keyListener);
|
|
@@ -823,6 +890,82 @@ class ReplManager {
|
|
|
823
890
|
console.log(lastMsg.content);
|
|
824
891
|
}
|
|
825
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
|
+
}
|
|
826
969
|
estimateCost(input, output) {
|
|
827
970
|
const config = this.configManager.getConfig();
|
|
828
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
|
+
}
|