@indiccoder/mentis-cli 1.0.9 → 1.1.1

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.
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: List all files in current directory
3
+ argument-hint: "[directory]"
4
+ ---
5
+ ## Usage
6
+ Use this command to list files: /ls [directory]
7
+
8
+ ## Instructions
9
+ List all files using the ls command. If $1 is provided, use it as the directory.
10
+
11
+ !`ls -la $ARGUMENTS`
12
+
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Custom Slash Commands System
4
+ * Users can define their own slash commands as markdown files
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ /**
3
+ * CommandCreator - Interactive wizard for creating new custom slash commands
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.CommandCreator = void 0;
43
+ exports.validateCommands = validateCommands;
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 CommandManager_1 = require("./CommandManager");
49
+ class CommandCreator {
50
+ constructor(commandManager) {
51
+ this.commandManager = commandManager || new CommandManager_1.CommandManager();
52
+ }
53
+ /**
54
+ * Run the interactive command creation wizard
55
+ */
56
+ async run(name) {
57
+ console.log('\nšŸ“ Create a new Custom Slash Command\n');
58
+ let commandName;
59
+ let commandType;
60
+ let description;
61
+ let allowedTools;
62
+ let argumentHint;
63
+ let namespace;
64
+ // Step 1: Command Name
65
+ if (name) {
66
+ commandName = name;
67
+ }
68
+ else {
69
+ const { name: inputName } = await inquirer_1.default.prompt([
70
+ {
71
+ type: 'input',
72
+ name: 'name',
73
+ message: 'Command name (lowercase, numbers, hyphens only):',
74
+ validate: (input) => {
75
+ if (!input)
76
+ return 'Name is required';
77
+ if (!/^[a-z0-9-]+$/.test(input)) {
78
+ return 'Name must contain only lowercase letters, numbers, and hyphens';
79
+ }
80
+ if (input.length > 64)
81
+ return 'Name must be 64 characters or less';
82
+ return true;
83
+ }
84
+ }
85
+ ]);
86
+ commandName = inputName;
87
+ }
88
+ // Step 2: Command Type
89
+ const { type } = await inquirer_1.default.prompt([
90
+ {
91
+ type: 'list',
92
+ name: 'type',
93
+ message: 'Command type:',
94
+ choices: [
95
+ { name: 'Personal (available in all projects)', value: 'personal' },
96
+ { name: 'Project (shared with team via git)', value: 'project' }
97
+ ],
98
+ default: 'personal'
99
+ }
100
+ ]);
101
+ commandType = type;
102
+ // Step 3: Namespace (optional, for grouping)
103
+ const { useNamespace } = await inquirer_1.default.prompt([
104
+ {
105
+ type: 'confirm',
106
+ name: 'useNamespace',
107
+ message: 'Add a namespace for grouping?',
108
+ default: false
109
+ }
110
+ ]);
111
+ if (useNamespace) {
112
+ const { ns } = await inquirer_1.default.prompt([
113
+ {
114
+ type: 'input',
115
+ name: 'ns',
116
+ message: 'Namespace (e.g., "git", "review", "test"):',
117
+ validate: (input) => {
118
+ if (!input)
119
+ return 'Namespace is required';
120
+ if (!/^[a-z0-9-]+$/.test(input)) {
121
+ return 'Namespace must contain only lowercase letters, numbers, and hyphens';
122
+ }
123
+ return true;
124
+ }
125
+ }
126
+ ]);
127
+ namespace = ns;
128
+ }
129
+ // Step 4: Description
130
+ const { desc } = await inquirer_1.default.prompt([
131
+ {
132
+ type: 'input',
133
+ name: 'desc',
134
+ message: 'Description:',
135
+ validate: (input) => {
136
+ if (!input)
137
+ return 'Description is required';
138
+ if (input.length > 1024)
139
+ return 'Description must be 1024 characters or less';
140
+ return true;
141
+ }
142
+ }
143
+ ]);
144
+ description = desc;
145
+ // Step 5: Arguments (optional)
146
+ const { useArgs } = await inquirer_1.default.prompt([
147
+ {
148
+ type: 'confirm',
149
+ name: 'useArgs',
150
+ message: 'Does this command accept arguments?',
151
+ default: false
152
+ }
153
+ ]);
154
+ if (useArgs) {
155
+ const { hint } = await inquirer_1.default.prompt([
156
+ {
157
+ type: 'input',
158
+ name: 'hint',
159
+ message: 'Argument hint (e.g., "<file>", "[options]"):',
160
+ validate: (input) => {
161
+ if (!input)
162
+ return 'Argument hint is required';
163
+ return true;
164
+ }
165
+ }
166
+ ]);
167
+ argumentHint = hint;
168
+ }
169
+ // Step 6: Allowed Tools (optional)
170
+ const { useAllowedTools } = await inquirer_1.default.prompt([
171
+ {
172
+ type: 'confirm',
173
+ name: 'useAllowedTools',
174
+ message: 'Restrict which tools this command can use?',
175
+ default: false
176
+ }
177
+ ]);
178
+ if (useAllowedTools) {
179
+ const { tools } = await inquirer_1.default.prompt([
180
+ {
181
+ type: 'checkbox',
182
+ name: 'tools',
183
+ message: 'Select allowed tools:',
184
+ choices: [
185
+ { name: 'Read (read_file)', value: 'Read' },
186
+ { name: 'Write (write_file)', value: 'Write' },
187
+ { name: 'Edit (edit_file)', value: 'Edit' },
188
+ { name: 'Grep (search files)', value: 'Grep' },
189
+ { name: 'Glob (find files)', value: 'Glob' },
190
+ { name: 'ListDir (list directory)', value: 'ListDir' },
191
+ { name: 'SearchFile (search in files)', value: 'SearchFile' },
192
+ { name: 'RunShell (run shell command)', value: 'RunShell' },
193
+ { name: 'WebSearch (web search)', value: 'WebSearch' },
194
+ { name: 'GitStatus', value: 'GitStatus' },
195
+ { name: 'GitDiff', value: 'GitDiff' },
196
+ { name: 'GitCommit', value: 'GitCommit' },
197
+ { name: 'GitPush', value: 'GitPush' },
198
+ { name: 'GitPull', value: 'GitPull' }
199
+ ]
200
+ }
201
+ ]);
202
+ allowedTools = tools.length > 0 ? tools : undefined;
203
+ }
204
+ // Step 7: Create the command
205
+ return this.createCommand(commandName, commandType, description, allowedTools, argumentHint, namespace);
206
+ }
207
+ /**
208
+ * Create the command file
209
+ */
210
+ async createCommand(name, type, description, allowedTools, argumentHint, namespace) {
211
+ const baseDir = type === 'personal'
212
+ ? path.join(os.homedir(), '.mentis', 'commands')
213
+ : path.join(process.cwd(), '.mentis', 'commands');
214
+ const commandDir = namespace ? path.join(baseDir, namespace) : baseDir;
215
+ const commandFile = path.join(commandDir, `${name}.md`);
216
+ // Check if command already exists
217
+ if (fs.existsSync(commandFile)) {
218
+ const { overwrite } = await inquirer_1.default.prompt([
219
+ {
220
+ type: 'confirm',
221
+ name: 'overwrite',
222
+ message: `Command "${name}" already exists. Overwrite?`,
223
+ default: false
224
+ }
225
+ ]);
226
+ if (!overwrite) {
227
+ console.log('Cancelled.');
228
+ return false;
229
+ }
230
+ }
231
+ // Create directory
232
+ if (!fs.existsSync(commandDir)) {
233
+ fs.mkdirSync(commandDir, { recursive: true });
234
+ }
235
+ // Generate command content
236
+ let content = `---\ndescription: ${description}\n`;
237
+ if (allowedTools && allowedTools.length > 0) {
238
+ content += `allowed-tools: [${allowedTools.map(t => `"${t}"`).join(', ')}]\n`;
239
+ }
240
+ if (argumentHint) {
241
+ content += `argument-hint: "${argumentHint}"\n`;
242
+ }
243
+ content += `---\n\n`;
244
+ // Add usage instructions in markdown
245
+ content += `## Usage\n\n`;
246
+ content += `Use this command by typing: /${name}${argumentHint ? ` ${argumentHint}` : ''}\n\n`;
247
+ content += `## Instructions\n\n`;
248
+ content += `Add your instructions here. You can use:\n`;
249
+ content += `- \`$1\`, \`$2\`, etc. for positional arguments\n`;
250
+ content += `- \`$ARGUMENTS\` for all arguments\n`;
251
+ content += `- \`\!\\\`command\`\` for bash commands\n`;
252
+ content += `- \`@file\` for file references\n\n`;
253
+ // Write command file
254
+ fs.writeFileSync(commandFile, content, 'utf-8');
255
+ console.log(`\nāœ“ Command created at: ${commandFile}`);
256
+ console.log(`\nNext steps:`);
257
+ console.log(` 1. Edit ${commandFile} to add instructions`);
258
+ console.log(` 2. Restart Mentis or use /commands validate to load the new command`);
259
+ return true;
260
+ }
261
+ }
262
+ exports.CommandCreator = CommandCreator;
263
+ /**
264
+ * Validate commands and show results
265
+ */
266
+ async function validateCommands(commandManager) {
267
+ const commands = commandManager.getAllCommands();
268
+ console.log('\nšŸ“‹ Command Validation Results\n');
269
+ if (commands.length === 0) {
270
+ console.log('No custom commands to validate.');
271
+ console.log('Create commands with: /commands create');
272
+ return;
273
+ }
274
+ for (const cmd of commands) {
275
+ const isValid = cmd.name && cmd.name.length > 0 && cmd.content.length > 0;
276
+ const icon = isValid ? 'āœ“' : 'āœ—';
277
+ console.log(`${icon} /${cmd.name} (${cmd.type})`);
278
+ if (!isValid) {
279
+ console.log(` ERROR: Invalid command structure`);
280
+ }
281
+ if (cmd.frontmatter['allowed-tools'] && cmd.frontmatter['allowed-tools'].length > 0) {
282
+ console.log(` Allowed tools: ${cmd.frontmatter['allowed-tools'].join(', ')}`);
283
+ }
284
+ }
285
+ console.log(`\nāœ“ Validated ${commands.length} commands`);
286
+ }
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ /**
3
+ * CommandManager - Discover, parse, and execute custom slash commands
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.CommandManager = void 0;
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
46
+ const fast_glob_1 = require("fast-glob");
47
+ const yaml_1 = __importDefault(require("yaml"));
48
+ class CommandManager {
49
+ constructor(cwd = process.cwd()) {
50
+ this.commands = new Map();
51
+ this.personalCommandsDir = path.join(os.homedir(), '.mentis', 'commands');
52
+ this.projectCommandsDir = path.join(cwd, '.mentis', 'commands');
53
+ }
54
+ /**
55
+ * Discover all commands from configured directories
56
+ */
57
+ async discoverCommands() {
58
+ const discovered = [];
59
+ // Personal commands
60
+ discovered.push(...await this.discoverCommandsInDirectory(this.personalCommandsDir, 'personal'));
61
+ // Project commands
62
+ discovered.push(...await this.discoverCommandsInDirectory(this.projectCommandsDir, 'project'));
63
+ // Store commands in map (project commands override personal)
64
+ for (const command of discovered) {
65
+ this.commands.set(command.name, command);
66
+ }
67
+ return Array.from(this.commands.values());
68
+ }
69
+ /**
70
+ * Discover commands in a specific directory
71
+ */
72
+ async discoverCommandsInDirectory(dir, type) {
73
+ if (!fs.existsSync(dir)) {
74
+ return [];
75
+ }
76
+ const commands = [];
77
+ try {
78
+ // Find all .md files in subdirectories
79
+ const commandFiles = await (0, fast_glob_1.glob)('**/*.md', {
80
+ cwd: dir,
81
+ absolute: true,
82
+ onlyFiles: true
83
+ });
84
+ for (const commandFile of commandFiles) {
85
+ const command = await this.parseCommandFile(commandFile, type);
86
+ if (command) {
87
+ commands.push(command);
88
+ }
89
+ }
90
+ }
91
+ catch (error) {
92
+ console.error(`Error discovering commands in ${dir}: ${error.message}`);
93
+ }
94
+ return commands;
95
+ }
96
+ /**
97
+ * Parse a command file
98
+ */
99
+ async parseCommandFile(commandPath, type) {
100
+ try {
101
+ const content = fs.readFileSync(commandPath, 'utf-8');
102
+ const frontmatter = this.extractFrontmatter(content);
103
+ const commandName = this.getCommandName(commandPath, type);
104
+ if (!commandName) {
105
+ return null;
106
+ }
107
+ // Get namespace (subdirectory)
108
+ const relativePath = path.relative(type === 'personal' ? this.personalCommandsDir : this.projectCommandsDir, commandPath);
109
+ const namespace = path.dirname(relativePath) !== '.' ? path.dirname(relativePath) : '';
110
+ const description = frontmatter.description || this.extractFirstLine(content);
111
+ const command = {
112
+ name: commandName,
113
+ type,
114
+ path: commandPath,
115
+ directory: path.dirname(commandPath),
116
+ frontmatter,
117
+ content: content,
118
+ description: namespace ? `${description} (${type}:${namespace})` : description,
119
+ hasParameters: content.includes('$1') || content.includes('$ARGUMENTS')
120
+ };
121
+ return command;
122
+ }
123
+ catch (error) {
124
+ console.error(`Error parsing command ${commandPath}: ${error.message}`);
125
+ return null;
126
+ }
127
+ }
128
+ /**
129
+ * Get command name from file path
130
+ */
131
+ getCommandName(commandPath, type) {
132
+ const relativePath = path.relative(type === 'personal' ? this.personalCommandsDir : this.projectCommandsDir, commandPath);
133
+ const nameWithExt = path.basename(relativePath); // e.g., "review.md"
134
+ const name = nameWithExt.replace(/\.md$/, ''); // e.g., "review"
135
+ if (!name || name.startsWith('.')) {
136
+ return null;
137
+ }
138
+ return name;
139
+ }
140
+ /**
141
+ * Extract YAML frontmatter from markdown content
142
+ */
143
+ extractFrontmatter(content) {
144
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/;
145
+ const match = content.match(frontmatterRegex);
146
+ if (!match) {
147
+ return {};
148
+ }
149
+ try {
150
+ const parsed = yaml_1.default.parse(match[1]);
151
+ return parsed;
152
+ }
153
+ catch (error) {
154
+ return {};
155
+ }
156
+ }
157
+ /**
158
+ * Extract first line of content as description
159
+ */
160
+ extractFirstLine(content) {
161
+ // Remove frontmatter
162
+ const withoutFrontmatter = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, '');
163
+ // Get first non-empty line
164
+ const lines = withoutFrontmatter.split('\n');
165
+ for (const line of lines) {
166
+ const trimmed = line.trim();
167
+ if (trimmed && !trimmed.startsWith('#')) {
168
+ return trimmed;
169
+ }
170
+ }
171
+ return 'No description';
172
+ }
173
+ /**
174
+ * Get command by name
175
+ */
176
+ getCommand(name) {
177
+ return this.commands.get(name);
178
+ }
179
+ /**
180
+ * Get all commands
181
+ */
182
+ getAllCommands() {
183
+ return Array.from(this.commands.values());
184
+ }
185
+ /**
186
+ * Get commands context for system prompt injection
187
+ */
188
+ getCommandsContext() {
189
+ const commands = this.getAllCommands();
190
+ if (commands.length === 0) {
191
+ return '';
192
+ }
193
+ const context = commands.map(cmd => {
194
+ let line = `/${cmd.name}`;
195
+ if (cmd.frontmatter['argument-hint']) {
196
+ line += ` ${cmd.frontmatter['argument-hint']}`;
197
+ }
198
+ line += `: ${cmd.description.replace(/\s*\([^)]+\)/, '')}`;
199
+ return line;
200
+ }).join('\n');
201
+ return `Available Custom Commands:\n${context}`;
202
+ }
203
+ /**
204
+ * Parse command content and execute substitutions
205
+ */
206
+ async parseCommand(command, args) {
207
+ let content = command.content;
208
+ const bashCommands = [];
209
+ const fileReferences = [];
210
+ // Remove frontmatter from content
211
+ content = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, '');
212
+ // Substitute $1, $2, etc.
213
+ content = this.substitutePositionalArgs(content, args);
214
+ // Substitute $ARGUMENTS
215
+ content = this.substituteAllArgs(content, args);
216
+ // Extract and collect bash commands (!`cmd`)
217
+ const bashRegex = /!`([^`]+)`/g;
218
+ let bashMatch;
219
+ while ((bashMatch = bashRegex.exec(content)) !== null) {
220
+ bashCommands.push(bashMatch[1]);
221
+ }
222
+ // Remove bash command markers
223
+ content = content.replace(/!`[^`]+`/g, '[BASH_OUTPUT]');
224
+ // Extract and collect file references (@file)
225
+ const fileRegex = /@([^\s]+)/g;
226
+ let fileMatch;
227
+ while ((fileMatch = fileRegex.exec(content)) !== null) {
228
+ fileReferences.push(fileMatch[1]);
229
+ }
230
+ return { content, bashCommands, fileReferences };
231
+ }
232
+ /**
233
+ * Substitute positional arguments ($1, $2, etc.)
234
+ */
235
+ substitutePositionalArgs(content, args) {
236
+ return content.replace(/\$(\d+)/g, (match, index) => {
237
+ const argIndex = parseInt(index) - 1;
238
+ return args[argIndex] || '';
239
+ });
240
+ }
241
+ /**
242
+ * Substitute $ARGUMENTS placeholder
243
+ */
244
+ substituteAllArgs(content, args) {
245
+ return content.replace(/\$ARGUMENTS/g, args.join(' '));
246
+ }
247
+ /**
248
+ * Ensure commands directories exist
249
+ */
250
+ ensureDirectoriesExist() {
251
+ if (!fs.existsSync(this.personalCommandsDir)) {
252
+ fs.mkdirSync(this.personalCommandsDir, { recursive: true });
253
+ }
254
+ }
255
+ /**
256
+ * Get personal commands directory path
257
+ */
258
+ getPersonalCommandsDir() {
259
+ return this.personalCommandsDir;
260
+ }
261
+ /**
262
+ * Get project commands directory path
263
+ */
264
+ getProjectCommandsDir() {
265
+ return this.projectCommandsDir;
266
+ }
267
+ }
268
+ exports.CommandManager = CommandManager;