@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 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
  }
@@ -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.displayLogo();
127
- UIManager_1.UIManager.displayWelcome();
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(); // readline expects newest first? No, newest is usually 0? Check.
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
- UIManager_1.UIManager.printSeparator();
142
- // console.log(chalk.dim(` /help for help | Model: ${chalk.cyan(this.currentModelName)}`));
143
- // Removed redundancy to keep CLI clean, prompt has info? No, prompt is minimal.
144
- const modeLabel = this.mode === 'PLAN' ? chalk_1.default.magenta('PLAN') : chalk_1.default.blue('BUILD');
145
- const modelInfo = this.currentModelName ? ` (${this.currentModelName})` : '';
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 (toolName === 'write_file') {
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,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
+ }