@indiccoder/mentis-cli 1.0.8 → 1.1.0
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/.mentis/commands/ls.md +12 -0
- package/dist/commands/Command.js +6 -0
- package/dist/commands/CommandCreator.js +286 -0
- package/dist/commands/CommandManager.js +268 -0
- package/dist/commands/SlashCommandTool.js +160 -0
- package/dist/repl/ReplManager.js +245 -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/dist/ui/UIManager.js +2 -2
- package/dist/utils/ContextVisualizer.js +92 -0
- package/dist/utils/ConversationCompacter.js +98 -0
- package/dist/utils/ProjectInitializer.js +181 -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 +4 -3
- package/src/commands/Command.ts +40 -0
- package/src/commands/CommandCreator.ts +281 -0
- package/src/commands/CommandManager.ts +280 -0
- package/src/commands/SlashCommandTool.ts +152 -0
- package/src/repl/ReplManager.ts +302 -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/src/ui/UIManager.ts +2 -2
- package/src/utils/ContextVisualizer.ts +105 -0
- package/src/utils/ConversationCompacter.ts +124 -0
- package/src/utils/ProjectInitializer.ts +170 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SlashCommandTool - Tool for model-invoked custom command execution
|
|
4
|
+
* The model can call this tool when it determines a custom command should be run
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.ListCommandsTool = exports.SlashCommandTool = void 0;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const util_1 = require("util");
|
|
44
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
45
|
+
class SlashCommandTool {
|
|
46
|
+
constructor(commandManager) {
|
|
47
|
+
this.name = 'slash_command';
|
|
48
|
+
this.description = 'Execute a custom slash command. Available commands can be seen in /help. Example: slash_command({ name: "review", arguments: ["src/file.ts"] })';
|
|
49
|
+
this.parameters = {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
name: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'The name of the custom command to execute'
|
|
55
|
+
},
|
|
56
|
+
arguments: {
|
|
57
|
+
type: 'array',
|
|
58
|
+
items: { type: 'string' },
|
|
59
|
+
description: 'Arguments to pass to the command'
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
required: ['name']
|
|
63
|
+
};
|
|
64
|
+
this.commandManager = commandManager;
|
|
65
|
+
}
|
|
66
|
+
async execute(args) {
|
|
67
|
+
const { name, arguments: cmdArgs = [] } = args;
|
|
68
|
+
if (!name) {
|
|
69
|
+
return 'Error: Command name is required';
|
|
70
|
+
}
|
|
71
|
+
const command = this.commandManager.getCommand(name);
|
|
72
|
+
if (!command) {
|
|
73
|
+
const availableCommands = this.commandManager.getAllCommands().map(c => c.name).join(', ');
|
|
74
|
+
return `Error: Command "${name}" not found. Available commands: ${availableCommands || 'none'}`;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
// Parse command and handle substitutions
|
|
78
|
+
const parsed = await this.commandManager.parseCommand(command, cmdArgs);
|
|
79
|
+
let content = parsed.content;
|
|
80
|
+
// Execute bash commands and collect results
|
|
81
|
+
const bashResults = [];
|
|
82
|
+
for (const bashCmd of parsed.bashCommands) {
|
|
83
|
+
try {
|
|
84
|
+
const result = await execAsync(bashCmd);
|
|
85
|
+
bashResults.push(`${bashCmd}\n${result.stdout}`);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
bashResults.push(`${bashCmd}\nError: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Substitute bash outputs into content
|
|
92
|
+
let bashIndex = 0;
|
|
93
|
+
content = content.replace(/\[BASH_OUTPUT\]/g, () => {
|
|
94
|
+
return bashResults[bashIndex++] || '';
|
|
95
|
+
});
|
|
96
|
+
// Read file references
|
|
97
|
+
const fileContents = [];
|
|
98
|
+
for (const filePath of parsed.fileReferences) {
|
|
99
|
+
try {
|
|
100
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
101
|
+
fileContents.push(`\n=== File: ${filePath} ===\n${fileContent}\n=== End of ${filePath} ===\n`);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
fileContents.push(`\n=== File: ${filePath} ===\nError: ${error.message}\n=== End of ${filePath} ===\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Substitute file references
|
|
108
|
+
let fileIndex = 0;
|
|
109
|
+
content = content.replace(/@[^\s]+/g, () => {
|
|
110
|
+
return fileContents[fileIndex++] || '';
|
|
111
|
+
});
|
|
112
|
+
return `# Executing: /${name}${cmdArgs.length ? ' ' + cmdArgs.join(' ') : ''}\n\n${content}`;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return `Error executing command "${name}": ${error.message}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.SlashCommandTool = SlashCommandTool;
|
|
120
|
+
/**
|
|
121
|
+
* ListCommandsTool - Tool for listing custom commands
|
|
122
|
+
*/
|
|
123
|
+
class ListCommandsTool {
|
|
124
|
+
constructor(commandManager) {
|
|
125
|
+
this.name = 'list_commands';
|
|
126
|
+
this.description = 'List all available custom slash commands with their descriptions';
|
|
127
|
+
this.parameters = {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {},
|
|
130
|
+
required: []
|
|
131
|
+
};
|
|
132
|
+
this.commandManager = commandManager;
|
|
133
|
+
}
|
|
134
|
+
async execute() {
|
|
135
|
+
const commands = this.commandManager.getAllCommands();
|
|
136
|
+
if (commands.length === 0) {
|
|
137
|
+
return 'No custom commands available. Add commands to ~/.mentis/commands/ or .mentis/commands/';
|
|
138
|
+
}
|
|
139
|
+
let response = `# Custom Commands (${commands.length})\n\n`;
|
|
140
|
+
// Group by namespace
|
|
141
|
+
const grouped = new Map();
|
|
142
|
+
for (const cmd of commands) {
|
|
143
|
+
const ns = cmd.description.match(/\(([^)]+)\)/)?.[1] || cmd.type;
|
|
144
|
+
if (!grouped.has(ns)) {
|
|
145
|
+
grouped.set(ns, []);
|
|
146
|
+
}
|
|
147
|
+
grouped.get(ns).push(cmd);
|
|
148
|
+
}
|
|
149
|
+
for (const [namespace, cmds] of grouped) {
|
|
150
|
+
response += `## ${namespace}\n\n`;
|
|
151
|
+
for (const cmd of cmds) {
|
|
152
|
+
const params = cmd.frontmatter['argument-hint'] ? ` ${cmd.frontmatter['argument-hint']}` : '';
|
|
153
|
+
response += `**/${cmd.name}${params}**\n`;
|
|
154
|
+
response += `${cmd.description}\n\n`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return response;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.ListCommandsTool = ListCommandsTool;
|
package/dist/repl/ReplManager.js
CHANGED
|
@@ -52,6 +52,13 @@ 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");
|
|
57
|
+
const ContextVisualizer_1 = require("../utils/ContextVisualizer");
|
|
58
|
+
const ProjectInitializer_1 = require("../utils/ProjectInitializer");
|
|
59
|
+
const ConversationCompacter_1 = require("../utils/ConversationCompacter");
|
|
60
|
+
const CommandManager_1 = require("../commands/CommandManager");
|
|
61
|
+
const SlashCommandTool_1 = require("../commands/SlashCommandTool");
|
|
55
62
|
const readline = __importStar(require("readline"));
|
|
56
63
|
const fs = __importStar(require("fs"));
|
|
57
64
|
const path = __importStar(require("path"));
|
|
@@ -66,10 +73,16 @@ class ReplManager {
|
|
|
66
73
|
this.tools = [];
|
|
67
74
|
this.mcpClients = [];
|
|
68
75
|
this.currentModelName = 'Unknown';
|
|
76
|
+
this.activeSkill = null; // Track currently active skill for allowed-tools
|
|
69
77
|
this.configManager = new ConfigManager_1.ConfigManager();
|
|
70
78
|
this.contextManager = new ContextManager_1.ContextManager();
|
|
71
79
|
this.checkpointManager = new CheckpointManager_1.CheckpointManager();
|
|
80
|
+
this.skillsManager = new SkillsManager_1.SkillsManager();
|
|
81
|
+
this.contextVisualizer = new ContextVisualizer_1.ContextVisualizer();
|
|
82
|
+
this.conversationCompacter = new ConversationCompacter_1.ConversationCompacter();
|
|
83
|
+
this.commandManager = new CommandManager_1.CommandManager();
|
|
72
84
|
this.shell = new PersistentShell_1.PersistentShell();
|
|
85
|
+
// Create tools array without skill tools first
|
|
73
86
|
this.tools = [
|
|
74
87
|
new FileTools_1.WriteFileTool(),
|
|
75
88
|
new FileTools_1.ReadFileTool(),
|
|
@@ -90,6 +103,62 @@ class ReplManager {
|
|
|
90
103
|
});
|
|
91
104
|
// Default to Ollama if not specified, assuming compatible endpoint
|
|
92
105
|
this.initializeClient();
|
|
106
|
+
// Initialize skills system after client is ready
|
|
107
|
+
this.initializeSkills();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Initialize the skills and custom commands system
|
|
111
|
+
*/
|
|
112
|
+
async initializeSkills() {
|
|
113
|
+
// Initialize skills
|
|
114
|
+
this.skillsManager.ensureDirectoriesExist();
|
|
115
|
+
await this.skillsManager.discoverSkills();
|
|
116
|
+
// Initialize custom commands
|
|
117
|
+
this.commandManager.ensureDirectoriesExist();
|
|
118
|
+
await this.commandManager.discoverCommands();
|
|
119
|
+
// Add skill tools to the tools list
|
|
120
|
+
// Pass callback to LoadSkillTool to track active skill
|
|
121
|
+
this.tools.push(new LoadSkillTool_1.LoadSkillTool(this.skillsManager, (skill) => {
|
|
122
|
+
this.activeSkill = skill ? skill.name : null;
|
|
123
|
+
}), new LoadSkillTool_1.ListSkillsTool(this.skillsManager), new LoadSkillTool_1.ReadSkillFileTool(this.skillsManager), new SlashCommandTool_1.SlashCommandTool(this.commandManager), new SlashCommandTool_1.ListCommandsTool(this.commandManager));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if a tool is allowed by the currently active skill
|
|
127
|
+
* Returns true if tool is allowed, false if it requires confirmation
|
|
128
|
+
*/
|
|
129
|
+
isToolAllowedBySkill(toolName) {
|
|
130
|
+
if (!this.activeSkill) {
|
|
131
|
+
// No active skill, all tools require confirmation as per normal flow
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const skill = this.skillsManager.getSkill(this.activeSkill);
|
|
135
|
+
if (!skill || !skill.allowedTools || skill.allowedTools.length === 0) {
|
|
136
|
+
// No skill or no allowed-tools restriction
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
// Map tool names to allowed tool names
|
|
140
|
+
const toolMapping = {
|
|
141
|
+
'write_file': 'Write',
|
|
142
|
+
'read_file': 'Read',
|
|
143
|
+
'edit_file': 'Edit',
|
|
144
|
+
'search_files': 'Grep',
|
|
145
|
+
'list_dir': 'ListDir',
|
|
146
|
+
'search_file': 'SearchFile',
|
|
147
|
+
'run_shell': 'RunShell',
|
|
148
|
+
'web_search': 'WebSearch',
|
|
149
|
+
'git_status': 'GitStatus',
|
|
150
|
+
'git_diff': 'GitDiff',
|
|
151
|
+
'git_commit': 'GitCommit',
|
|
152
|
+
'git_push': 'GitPush',
|
|
153
|
+
'git_pull': 'GitPull',
|
|
154
|
+
'load_skill': 'Read',
|
|
155
|
+
'list_skills': 'Read',
|
|
156
|
+
'read_skill_file': 'Read',
|
|
157
|
+
'slash_command': 'Read',
|
|
158
|
+
'list_commands': 'Read'
|
|
159
|
+
};
|
|
160
|
+
const mappedToolName = toolMapping[toolName] || toolName;
|
|
161
|
+
return skill.allowedTools.includes(mappedToolName);
|
|
93
162
|
}
|
|
94
163
|
initializeClient() {
|
|
95
164
|
const config = this.configManager.getConfig();
|
|
@@ -197,16 +266,17 @@ class ReplManager {
|
|
|
197
266
|
console.log(' /drop <file> - Remove file from context');
|
|
198
267
|
console.log(' /plan - Switch to PLAN mode');
|
|
199
268
|
console.log(' /build - Switch to BUILD mode');
|
|
200
|
-
console.log(' /plan - Switch to PLAN mode');
|
|
201
|
-
console.log(' /build - Switch to BUILD mode');
|
|
202
269
|
console.log(' /model - Interactively select Provider & Model');
|
|
203
270
|
console.log(' /use <provider> [model] - Quick switch (legacy)');
|
|
204
271
|
console.log(' /mcp <cmd> - Manage MCP servers');
|
|
272
|
+
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
273
|
+
console.log(' /commands <list|create|validate> - Manage Custom Commands');
|
|
205
274
|
console.log(' /resume - Resume last session');
|
|
206
275
|
console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
|
|
207
276
|
console.log(' /search <query> - Search codebase');
|
|
208
277
|
console.log(' /run <cmd> - Run shell command');
|
|
209
278
|
console.log(' /commit [msg] - Git commit all changes');
|
|
279
|
+
console.log(' /init - Initialize project with .mentis.md');
|
|
210
280
|
break;
|
|
211
281
|
case '/plan':
|
|
212
282
|
this.mode = 'PLAN';
|
|
@@ -278,12 +348,23 @@ class ReplManager {
|
|
|
278
348
|
const updater = new UpdateManager();
|
|
279
349
|
await updater.checkAndPerformUpdate(true);
|
|
280
350
|
break;
|
|
351
|
+
case '/init':
|
|
352
|
+
await this.handleInitCommand();
|
|
353
|
+
break;
|
|
354
|
+
case '/skills':
|
|
355
|
+
await this.handleSkillsCommand(args);
|
|
356
|
+
break;
|
|
357
|
+
case '/commands':
|
|
358
|
+
await this.handleCommandsCommand(args);
|
|
359
|
+
break;
|
|
281
360
|
default:
|
|
282
361
|
console.log(chalk_1.default.red(`Unknown command: ${command}`));
|
|
283
362
|
}
|
|
284
363
|
}
|
|
285
364
|
async handleChat(input) {
|
|
286
365
|
const context = this.contextManager.getContextString();
|
|
366
|
+
const skillsContext = this.skillsManager.getSkillsContext();
|
|
367
|
+
const commandsContext = this.commandManager.getCommandsContext();
|
|
287
368
|
let fullInput = input;
|
|
288
369
|
let modeInstruction = '';
|
|
289
370
|
if (this.mode === 'PLAN') {
|
|
@@ -293,6 +374,14 @@ class ReplManager {
|
|
|
293
374
|
modeInstruction = '\n[SYSTEM: You are in BUILD mode. Focus on implementing working code that solves the user request efficiently.]';
|
|
294
375
|
}
|
|
295
376
|
fullInput = `${input}${modeInstruction}`;
|
|
377
|
+
// Add skills context if available
|
|
378
|
+
if (skillsContext) {
|
|
379
|
+
fullInput = `${skillsContext}\n\n${fullInput}`;
|
|
380
|
+
}
|
|
381
|
+
// Add commands context if available
|
|
382
|
+
if (commandsContext) {
|
|
383
|
+
fullInput = `${commandsContext}\n\n${fullInput}`;
|
|
384
|
+
}
|
|
296
385
|
if (context) {
|
|
297
386
|
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
298
387
|
}
|
|
@@ -345,7 +434,8 @@ class ReplManager {
|
|
|
345
434
|
}
|
|
346
435
|
console.log(chalk_1.default.dim(` [Action] ${toolName}(${displayArgs})`));
|
|
347
436
|
// Safety check for write_file
|
|
348
|
-
if
|
|
437
|
+
// Skip confirmation if tool is allowed by active skill
|
|
438
|
+
if (toolName === 'write_file' && !this.isToolAllowedBySkill('Write')) {
|
|
349
439
|
// Pause cancellation listener during user interaction
|
|
350
440
|
if (process.stdin.isTTY) {
|
|
351
441
|
process.stdin.removeListener('keypress', keyListener);
|
|
@@ -431,8 +521,16 @@ class ReplManager {
|
|
|
431
521
|
const totalCost = this.estimateCost(input_tokens, output_tokens);
|
|
432
522
|
console.log(chalk_1.default.dim(`\n(Tokens: ${input_tokens} in / ${output_tokens} out | Est. Cost: $${totalCost.toFixed(5)})`));
|
|
433
523
|
}
|
|
524
|
+
// Display context bar
|
|
525
|
+
const contextBar = this.contextVisualizer.getContextBar(this.history);
|
|
526
|
+
console.log(chalk_1.default.dim(`\n${contextBar}`));
|
|
434
527
|
console.log('');
|
|
435
528
|
this.history.push({ role: 'assistant', content: response.content });
|
|
529
|
+
// Auto-compact prompt when context is at 80%
|
|
530
|
+
const usage = this.contextVisualizer.calculateUsage(this.history);
|
|
531
|
+
if (usage.percentage >= 80) {
|
|
532
|
+
this.history = await this.conversationCompacter.promptIfCompactNeeded(usage.percentage, this.history, this.modelClient);
|
|
533
|
+
}
|
|
436
534
|
}
|
|
437
535
|
}
|
|
438
536
|
catch (error) {
|
|
@@ -827,6 +925,150 @@ class ReplManager {
|
|
|
827
925
|
console.log(lastMsg.content);
|
|
828
926
|
}
|
|
829
927
|
}
|
|
928
|
+
async handleSkillsCommand(args) {
|
|
929
|
+
const { SkillCreator, validateSkills } = await Promise.resolve().then(() => __importStar(require('../skills/SkillCreator')));
|
|
930
|
+
if (args.length < 1) {
|
|
931
|
+
// Show skills list by default
|
|
932
|
+
await this.handleSkillsCommand(['list']);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const action = args[0];
|
|
936
|
+
switch (action) {
|
|
937
|
+
case 'list':
|
|
938
|
+
await this.handleSkillsList();
|
|
939
|
+
break;
|
|
940
|
+
case 'show':
|
|
941
|
+
if (args.length < 2) {
|
|
942
|
+
console.log(chalk_1.default.red('Usage: /skills show <name>'));
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
await this.handleSkillsShow(args[1]);
|
|
946
|
+
break;
|
|
947
|
+
case 'create':
|
|
948
|
+
const creator = new SkillCreator(this.skillsManager);
|
|
949
|
+
await creator.run(args[1]);
|
|
950
|
+
// Re-discover skills after creation
|
|
951
|
+
await this.skillsManager.discoverSkills();
|
|
952
|
+
break;
|
|
953
|
+
case 'validate':
|
|
954
|
+
await validateSkills(this.skillsManager);
|
|
955
|
+
break;
|
|
956
|
+
default:
|
|
957
|
+
console.log(chalk_1.default.red(`Unknown skills action: ${action}`));
|
|
958
|
+
console.log(chalk_1.default.yellow('Available actions: list, show, create, validate'));
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
async handleSkillsList() {
|
|
962
|
+
const skills = this.skillsManager.getAllSkills();
|
|
963
|
+
if (skills.length === 0) {
|
|
964
|
+
console.log(chalk_1.default.yellow('No skills available.'));
|
|
965
|
+
console.log(chalk_1.default.dim('Create skills with: /skills create'));
|
|
966
|
+
console.log(chalk_1.default.dim('Add skills to: ~/.mentis/skills/ or .mentis/skills/'));
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
console.log(chalk_1.default.cyan(`\nAvailable Skills (${skills.length}):\n`));
|
|
970
|
+
for (const skill of skills) {
|
|
971
|
+
const statusIcon = skill.isValid ? '✓' : '✗';
|
|
972
|
+
const typeLabel = skill.type === 'personal' ? 'Personal' : 'Project';
|
|
973
|
+
console.log(`${statusIcon} ${chalk_1.default.bold(skill.name)} (${typeLabel})`);
|
|
974
|
+
console.log(` ${skill.description}`);
|
|
975
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
976
|
+
console.log(chalk_1.default.dim(` Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
977
|
+
}
|
|
978
|
+
if (!skill.isValid && skill.errors) {
|
|
979
|
+
console.log(chalk_1.default.red(` Errors: ${skill.errors.join(', ')}`));
|
|
980
|
+
}
|
|
981
|
+
console.log('');
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
async handleSkillsShow(name) {
|
|
985
|
+
const skill = await this.skillsManager.loadFullSkill(name);
|
|
986
|
+
if (!skill) {
|
|
987
|
+
console.log(chalk_1.default.red(`Skill "${name}" not found.`));
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
console.log(chalk_1.default.cyan(`\n# ${skill.name}\n`));
|
|
991
|
+
console.log(chalk_1.default.dim(`Type: ${skill.type}`));
|
|
992
|
+
console.log(chalk_1.default.dim(`Path: ${skill.path}`));
|
|
993
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
994
|
+
console.log(chalk_1.default.dim(`Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
995
|
+
}
|
|
996
|
+
console.log('');
|
|
997
|
+
console.log(skill.content || 'No content available');
|
|
998
|
+
// List supporting files
|
|
999
|
+
const files = this.skillsManager.listSkillFiles(name);
|
|
1000
|
+
if (files.length > 0) {
|
|
1001
|
+
console.log(chalk_1.default.dim(`\nSupporting files: ${files.join(', ')}`));
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
async handleInitCommand() {
|
|
1005
|
+
const initializer = new ProjectInitializer_1.ProjectInitializer();
|
|
1006
|
+
await initializer.run();
|
|
1007
|
+
}
|
|
1008
|
+
async handleCommandsCommand(args) {
|
|
1009
|
+
if (args.length < 1) {
|
|
1010
|
+
// Show commands list by default
|
|
1011
|
+
await this.handleCommandsCommand(['list']);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const action = args[0];
|
|
1015
|
+
switch (action) {
|
|
1016
|
+
case 'list':
|
|
1017
|
+
await this.handleCommandsList();
|
|
1018
|
+
break;
|
|
1019
|
+
case 'create':
|
|
1020
|
+
await this.handleCommandsCreate(args[1]);
|
|
1021
|
+
break;
|
|
1022
|
+
case 'validate':
|
|
1023
|
+
await this.handleCommandsValidate();
|
|
1024
|
+
break;
|
|
1025
|
+
default:
|
|
1026
|
+
console.log(chalk_1.default.red(`Unknown commands action: ${action}`));
|
|
1027
|
+
console.log(chalk_1.default.yellow('Available actions: list, create, validate'));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
async handleCommandsList() {
|
|
1031
|
+
const commands = this.commandManager.getAllCommands();
|
|
1032
|
+
if (commands.length === 0) {
|
|
1033
|
+
console.log(chalk_1.default.yellow('No custom commands available.'));
|
|
1034
|
+
console.log(chalk_1.default.dim('Create commands with: /commands create'));
|
|
1035
|
+
console.log(chalk_1.default.dim('Add commands to: ~/.mentis/commands/ or .mentis/commands/'));
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
console.log(chalk_1.default.cyan(`\nCustom Commands (${commands.length}):\n`));
|
|
1039
|
+
// Group by namespace
|
|
1040
|
+
const grouped = new Map();
|
|
1041
|
+
for (const cmd of commands) {
|
|
1042
|
+
const ns = cmd.description.match(/\(([^)]+)\)/)?.[1] || cmd.type;
|
|
1043
|
+
if (!grouped.has(ns)) {
|
|
1044
|
+
grouped.set(ns, []);
|
|
1045
|
+
}
|
|
1046
|
+
grouped.get(ns).push(cmd);
|
|
1047
|
+
}
|
|
1048
|
+
for (const [namespace, cmds] of grouped) {
|
|
1049
|
+
console.log(chalk_1.default.bold(`\n${namespace}`));
|
|
1050
|
+
for (const cmd of cmds) {
|
|
1051
|
+
const params = cmd.frontmatter['argument-hint'] ? ` ${cmd.frontmatter['argument-hint']}` : '';
|
|
1052
|
+
console.log(` /${cmd.name}${params}`);
|
|
1053
|
+
console.log(` ${cmd.description.replace(/\s*\([^)]+\)/, '')}`);
|
|
1054
|
+
if (cmd.frontmatter['allowed-tools'] && cmd.frontmatter['allowed-tools'].length > 0) {
|
|
1055
|
+
console.log(chalk_1.default.dim(` Allowed tools: ${cmd.frontmatter['allowed-tools'].join(', ')}`));
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
console.log('');
|
|
1060
|
+
}
|
|
1061
|
+
async handleCommandsCreate(name) {
|
|
1062
|
+
const { CommandCreator } = await Promise.resolve().then(() => __importStar(require('../commands/CommandCreator')));
|
|
1063
|
+
const creator = new CommandCreator(this.commandManager);
|
|
1064
|
+
await creator.run(name);
|
|
1065
|
+
// Re-discover commands after creation
|
|
1066
|
+
await this.commandManager.discoverCommands();
|
|
1067
|
+
}
|
|
1068
|
+
async handleCommandsValidate() {
|
|
1069
|
+
const { validateCommands } = await Promise.resolve().then(() => __importStar(require('../commands/CommandCreator')));
|
|
1070
|
+
await validateCommands(this.commandManager);
|
|
1071
|
+
}
|
|
830
1072
|
estimateCost(input, output) {
|
|
831
1073
|
const config = this.configManager.getConfig();
|
|
832
1074
|
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;
|