@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.
@@ -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 (toolName === 'write_file') {
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,6 @@
1
+ "use strict";
2
+ /**
3
+ * Skill data structure for Agent Skills system
4
+ * Based on Claude Code's Agent Skills format
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }