@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.
- package/dist/repl/ReplManager.js +142 -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/docs/SKILLS.md +319 -0
- package/examples/skills/code-reviewer/SKILL.md +88 -0
- package/examples/skills/commit-helper/SKILL.md +66 -0
- package/examples/skills/pdf-processing/SKILL.md +108 -0
- package/package.json +3 -2
- package/src/repl/ReplManager.ts +174 -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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-reviewer
|
|
3
|
+
description: Reviews code for best practices, potential bugs, security issues, and improvements. Use when reviewing code, checking pull requests, or analyzing code quality.
|
|
4
|
+
allowed-tools: ["Read", "Grep", "Glob"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Code Reviewer
|
|
8
|
+
|
|
9
|
+
This skill provides systematic code review to identify issues and suggest improvements.
|
|
10
|
+
|
|
11
|
+
## Review Checklist
|
|
12
|
+
|
|
13
|
+
### 1. Code Organization
|
|
14
|
+
- [ ] Single Responsibility Principle followed
|
|
15
|
+
- [ ] Clear and descriptive names
|
|
16
|
+
- [ ] Proper separation of concerns
|
|
17
|
+
- [ ] Appropriate abstraction levels
|
|
18
|
+
|
|
19
|
+
### 2. Error Handling
|
|
20
|
+
- [ ] Proper error handling for all operations
|
|
21
|
+
- [ ] Meaningful error messages
|
|
22
|
+
- [ ] Graceful degradation
|
|
23
|
+
- [ ] No silent failures
|
|
24
|
+
|
|
25
|
+
### 3. Performance
|
|
26
|
+
- [ ] No obvious performance issues
|
|
27
|
+
- [ ] Appropriate data structures used
|
|
28
|
+
- [ ] Caching where applicable
|
|
29
|
+
- [ ] Resource cleanup (no memory leaks)
|
|
30
|
+
|
|
31
|
+
### 4. Security
|
|
32
|
+
- [ ] Input validation
|
|
33
|
+
- [ ] No hardcoded credentials
|
|
34
|
+
- [ ] SQL injection prevention
|
|
35
|
+
- [ ] XSS prevention (web apps)
|
|
36
|
+
- [ ] Proper authentication/authorization
|
|
37
|
+
|
|
38
|
+
### 5. Testing
|
|
39
|
+
- [ ] Test coverage adequate
|
|
40
|
+
- [ ] Edge cases considered
|
|
41
|
+
- [ ] Error scenarios tested
|
|
42
|
+
|
|
43
|
+
### 6. Documentation
|
|
44
|
+
- [ ] Complex logic explained
|
|
45
|
+
- [ ] Public API documented
|
|
46
|
+
- [ ] Usage examples provided
|
|
47
|
+
|
|
48
|
+
## How to Review
|
|
49
|
+
|
|
50
|
+
1. **Understand the purpose**: What is this code supposed to do?
|
|
51
|
+
2. **Read the code**: Follow the execution flow
|
|
52
|
+
3. **Check against checklist**: Go through each category
|
|
53
|
+
4. **Provide feedback**:
|
|
54
|
+
- **Critical**: Must fix before merge
|
|
55
|
+
- **Important**: Should fix
|
|
56
|
+
- **Suggestion**: Nice to have
|
|
57
|
+
5. **Explain why**: Don't just point out problems
|
|
58
|
+
|
|
59
|
+
## Feedback Format
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
## Critical Issues
|
|
63
|
+
- Issue description
|
|
64
|
+
- Location: file.ts:123
|
|
65
|
+
- Why: [reason]
|
|
66
|
+
- Suggestion: [fix]
|
|
67
|
+
|
|
68
|
+
## Important Notes
|
|
69
|
+
- Note description
|
|
70
|
+
- Location: file.ts:456
|
|
71
|
+
|
|
72
|
+
## Suggestions
|
|
73
|
+
- Suggestion description
|
|
74
|
+
- Could improve [aspect]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Common Issues to Look For
|
|
78
|
+
|
|
79
|
+
| Issue | Example |
|
|
80
|
+
|-------|---------|
|
|
81
|
+
| Unhandled promises | No `.catch()` or `await` without try/catch |
|
|
82
|
+
| Missing null checks | Accessing properties without null check |
|
|
83
|
+
| Race conditions | Async operations without proper ordering |
|
|
84
|
+
| Resource leaks | File handles, connections not closed |
|
|
85
|
+
| Type coercion | Using `==` instead of `===` |
|
|
86
|
+
| Magic numbers | Unexplained numeric literals |
|
|
87
|
+
| Large functions | Functions > 50 lines |
|
|
88
|
+
| Deep nesting | More than 3 levels of nesting |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit-helper
|
|
3
|
+
description: Generates clear, conventional commit messages from git diffs. Use when writing commit messages, reviewing staged changes, or when the user asks for help with git commits.
|
|
4
|
+
allowed-tools: ["GitStatus", "GitDiff", "GitCommit"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Commit Message Helper
|
|
8
|
+
|
|
9
|
+
This skill helps you write clear, informative git commit messages following conventional commit format.
|
|
10
|
+
|
|
11
|
+
## Instructions
|
|
12
|
+
|
|
13
|
+
When the user wants to commit changes:
|
|
14
|
+
|
|
15
|
+
1. **Check git status** to see what files are staged
|
|
16
|
+
2. **Review the diff** to understand what changed
|
|
17
|
+
3. **Generate a commit message** with:
|
|
18
|
+
- **Type**: One of `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
|
19
|
+
- **Scope**: (optional) The component or module affected
|
|
20
|
+
- **Summary**: Brief description under 50 characters
|
|
21
|
+
- **Body**: Detailed explanation of what and why (not how)
|
|
22
|
+
- **Footer**: (optional) Breaking changes or references
|
|
23
|
+
|
|
24
|
+
## Commit Types
|
|
25
|
+
|
|
26
|
+
| Type | Description |
|
|
27
|
+
|------|-------------|
|
|
28
|
+
| `feat` | New feature |
|
|
29
|
+
| `fix` | Bug fix |
|
|
30
|
+
| `docs` | Documentation changes |
|
|
31
|
+
| `style` | Code style changes (formatting, etc.) |
|
|
32
|
+
| `refactor` | Code refactoring |
|
|
33
|
+
| `test` | Adding or updating tests |
|
|
34
|
+
| `chore` | Maintenance tasks |
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
### Feature Addition
|
|
39
|
+
```
|
|
40
|
+
feat(api): add user authentication endpoint
|
|
41
|
+
|
|
42
|
+
Add OAuth2 authentication for the REST API.
|
|
43
|
+
Implements login, logout, and token refresh.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Bug Fix
|
|
47
|
+
```
|
|
48
|
+
fix(cli): prevent crash when config file is missing
|
|
49
|
+
|
|
50
|
+
Check for config file existence before reading.
|
|
51
|
+
Show helpful error message if file not found.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Documentation
|
|
55
|
+
```
|
|
56
|
+
docs: update README with new installation instructions
|
|
57
|
+
|
|
58
|
+
Clarify NPM installation steps and add troubleshooting section.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Best Practices
|
|
62
|
+
|
|
63
|
+
- Use present tense ("add" not "added")
|
|
64
|
+
- Explain what and why, not how
|
|
65
|
+
- Keep summary under 50 characters
|
|
66
|
+
- Reference issues in footer: `Closes #123`
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pdf-processing
|
|
3
|
+
description: Extract text, fill forms, merge PDFs, and manipulate PDF documents. Use when working with PDF files, forms, or document extraction. Requires pdf-parse package.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PDF Processing
|
|
7
|
+
|
|
8
|
+
This skill provides PDF manipulation capabilities for text extraction, form filling, and document operations.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
Install required packages:
|
|
13
|
+
```bash
|
|
14
|
+
npm install pdf-parse
|
|
15
|
+
# or
|
|
16
|
+
pnpm add pdf-parse
|
|
17
|
+
# or
|
|
18
|
+
yarn add pdf-parse
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Capabilities
|
|
22
|
+
|
|
23
|
+
### 1. Text Extraction
|
|
24
|
+
Extract plain text from PDF files with page-by-page information.
|
|
25
|
+
|
|
26
|
+
### 2. Form Field Detection
|
|
27
|
+
Identify and extract form fields from PDF documents.
|
|
28
|
+
|
|
29
|
+
### 3. Metadata Reading
|
|
30
|
+
Access PDF metadata like author, creation date, title.
|
|
31
|
+
|
|
32
|
+
### 4. Page Count & Info
|
|
33
|
+
Get total pages and document dimensions.
|
|
34
|
+
|
|
35
|
+
## Common Operations
|
|
36
|
+
|
|
37
|
+
### Extract All Text
|
|
38
|
+
```typescript
|
|
39
|
+
import fs from 'fs';
|
|
40
|
+
import pdf from 'pdf-parse';
|
|
41
|
+
|
|
42
|
+
const dataBuffer = fs.readFileSync('document.pdf');
|
|
43
|
+
const data = await pdf(dataBuffer);
|
|
44
|
+
|
|
45
|
+
console.log(data.text); // Full text content
|
|
46
|
+
console.log(data.numpages); // Number of pages
|
|
47
|
+
console.log(data.info); // PDF metadata
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Extract Text by Page
|
|
51
|
+
```typescript
|
|
52
|
+
const data = await pdf(dataBuffer);
|
|
53
|
+
// Text includes page breaks
|
|
54
|
+
// Split by page markers if needed
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Get PDF Info
|
|
58
|
+
```typescript
|
|
59
|
+
const data = await pdf(dataBuffer);
|
|
60
|
+
console.log({
|
|
61
|
+
pages: data.numpages,
|
|
62
|
+
info: data.info,
|
|
63
|
+
metadata: data.metadata
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Troubleshooting
|
|
68
|
+
|
|
69
|
+
### "pdf-parse not found"
|
|
70
|
+
Install the package: `npm install pdf-parse`
|
|
71
|
+
|
|
72
|
+
### Scanned PDFs return no text
|
|
73
|
+
Scanned PDFs are images. Use OCR (tesseract.js) instead.
|
|
74
|
+
|
|
75
|
+
### Encrypted PDFs
|
|
76
|
+
Password-protected PDFs cannot be read. Remove password first.
|
|
77
|
+
|
|
78
|
+
### Memory Issues with Large Files
|
|
79
|
+
For very large PDFs (>100MB), process in chunks or use streaming.
|
|
80
|
+
|
|
81
|
+
## Advanced Topics
|
|
82
|
+
|
|
83
|
+
For advanced PDF operations like filling forms, merging, or creating PDFs, see [ADVANCED.md](ADVANCED.md).
|
|
84
|
+
|
|
85
|
+
## Examples
|
|
86
|
+
|
|
87
|
+
### Quick Text Extraction
|
|
88
|
+
```bash
|
|
89
|
+
# User asks: "Extract text from this PDF"
|
|
90
|
+
# 1. Read the PDF file
|
|
91
|
+
# 2. Use pdf-parse to extract text
|
|
92
|
+
# 3. Return the text content
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Get Page Count
|
|
96
|
+
```bash
|
|
97
|
+
# User asks: "How many pages in this PDF?"
|
|
98
|
+
# 1. Parse the PDF
|
|
99
|
+
# 2. Return numpages value
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Form Analysis
|
|
103
|
+
```bash
|
|
104
|
+
# User asks: "What fields are in this PDF form?"
|
|
105
|
+
# 1. Parse the PDF
|
|
106
|
+
# 2. Look for form field patterns
|
|
107
|
+
# 3. List detected fields
|
|
108
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indiccoder/mentis-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"screenshot-desktop": "^1.15.3",
|
|
51
51
|
"uuid": "^13.0.0",
|
|
52
52
|
"vscode-ripgrep": "^1.13.2",
|
|
53
|
-
"xlsx": "^0.18.5"
|
|
53
|
+
"xlsx": "^0.18.5",
|
|
54
|
+
"yaml": "^2.7.0"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@types/figlet": "^1.7.0",
|
package/src/repl/ReplManager.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { Tool } from '../tools/Tool';
|
|
|
17
17
|
import { McpClient } from '../mcp/McpClient';
|
|
18
18
|
|
|
19
19
|
import { CheckpointManager } from '../checkpoint/CheckpointManager';
|
|
20
|
+
import { SkillsManager } from '../skills/SkillsManager';
|
|
21
|
+
import { LoadSkillTool, ListSkillsTool, ReadSkillFileTool } from '../skills/LoadSkillTool';
|
|
20
22
|
import * as readline from 'readline';
|
|
21
23
|
import * as fs from 'fs';
|
|
22
24
|
import * as path from 'path';
|
|
@@ -31,18 +33,23 @@ export class ReplManager {
|
|
|
31
33
|
private modelClient!: ModelClient;
|
|
32
34
|
private contextManager: ContextManager;
|
|
33
35
|
private checkpointManager: CheckpointManager;
|
|
36
|
+
private skillsManager: SkillsManager;
|
|
34
37
|
private history: ChatMessage[] = [];
|
|
35
38
|
private mode: 'PLAN' | 'BUILD' = 'BUILD';
|
|
36
39
|
private tools: Tool[] = [];
|
|
37
40
|
private mcpClients: McpClient[] = [];
|
|
38
41
|
private shell: PersistentShell;
|
|
39
42
|
private currentModelName: string = 'Unknown';
|
|
43
|
+
private activeSkill: string | null = null; // Track currently active skill for allowed-tools
|
|
40
44
|
|
|
41
45
|
constructor() {
|
|
42
46
|
this.configManager = new ConfigManager();
|
|
43
47
|
this.contextManager = new ContextManager();
|
|
44
48
|
this.checkpointManager = new CheckpointManager();
|
|
49
|
+
this.skillsManager = new SkillsManager();
|
|
45
50
|
this.shell = new PersistentShell();
|
|
51
|
+
|
|
52
|
+
// Create tools array without skill tools first
|
|
46
53
|
this.tools = [
|
|
47
54
|
new WriteFileTool(),
|
|
48
55
|
new ReadFileTool(),
|
|
@@ -64,6 +71,67 @@ export class ReplManager {
|
|
|
64
71
|
});
|
|
65
72
|
// Default to Ollama if not specified, assuming compatible endpoint
|
|
66
73
|
this.initializeClient();
|
|
74
|
+
|
|
75
|
+
// Initialize skills system after client is ready
|
|
76
|
+
this.initializeSkills();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Initialize the skills system
|
|
81
|
+
*/
|
|
82
|
+
private async initializeSkills() {
|
|
83
|
+
this.skillsManager.ensureDirectoriesExist();
|
|
84
|
+
await this.skillsManager.discoverSkills();
|
|
85
|
+
|
|
86
|
+
// Add skill tools to the tools list
|
|
87
|
+
// Pass callback to LoadSkillTool to track active skill
|
|
88
|
+
this.tools.push(
|
|
89
|
+
new LoadSkillTool(this.skillsManager, (skill) => {
|
|
90
|
+
this.activeSkill = skill ? skill.name : null;
|
|
91
|
+
}),
|
|
92
|
+
new ListSkillsTool(this.skillsManager),
|
|
93
|
+
new ReadSkillFileTool(this.skillsManager)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if a tool is allowed by the currently active skill
|
|
99
|
+
* Returns true if tool is allowed, false if it requires confirmation
|
|
100
|
+
*/
|
|
101
|
+
private isToolAllowedBySkill(toolName: string): boolean {
|
|
102
|
+
if (!this.activeSkill) {
|
|
103
|
+
// No active skill, all tools require confirmation as per normal flow
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const skill = this.skillsManager.getSkill(this.activeSkill);
|
|
108
|
+
if (!skill || !skill.allowedTools || skill.allowedTools.length === 0) {
|
|
109
|
+
// No skill or no allowed-tools restriction
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Map tool names to allowed tool names
|
|
114
|
+
const toolMapping: Record<string, string> = {
|
|
115
|
+
'write_file': 'Write',
|
|
116
|
+
'read_file': 'Read',
|
|
117
|
+
'edit_file': 'Edit',
|
|
118
|
+
'search_files': 'Grep',
|
|
119
|
+
'list_dir': 'ListDir',
|
|
120
|
+
'search_file': 'SearchFile',
|
|
121
|
+
'run_shell': 'RunShell',
|
|
122
|
+
'web_search': 'WebSearch',
|
|
123
|
+
'git_status': 'GitStatus',
|
|
124
|
+
'git_diff': 'GitDiff',
|
|
125
|
+
'git_commit': 'GitCommit',
|
|
126
|
+
'git_push': 'GitPush',
|
|
127
|
+
'git_pull': 'GitPull',
|
|
128
|
+
'load_skill': 'Read',
|
|
129
|
+
'list_skills': 'Read',
|
|
130
|
+
'read_skill_file': 'Read'
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const mappedToolName = toolMapping[toolName] || toolName;
|
|
134
|
+
return skill.allowedTools.includes(mappedToolName);
|
|
67
135
|
}
|
|
68
136
|
|
|
69
137
|
private initializeClient() {
|
|
@@ -184,11 +252,10 @@ export class ReplManager {
|
|
|
184
252
|
console.log(' /drop <file> - Remove file from context');
|
|
185
253
|
console.log(' /plan - Switch to PLAN mode');
|
|
186
254
|
console.log(' /build - Switch to BUILD mode');
|
|
187
|
-
console.log(' /plan - Switch to PLAN mode');
|
|
188
|
-
console.log(' /build - Switch to BUILD mode');
|
|
189
255
|
console.log(' /model - Interactively select Provider & Model');
|
|
190
256
|
console.log(' /use <provider> [model] - Quick switch (legacy)');
|
|
191
257
|
console.log(' /mcp <cmd> - Manage MCP servers');
|
|
258
|
+
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
192
259
|
console.log(' /resume - Resume last session');
|
|
193
260
|
console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
|
|
194
261
|
console.log(' /search <query> - Search codebase');
|
|
@@ -263,6 +330,9 @@ export class ReplManager {
|
|
|
263
330
|
const updater = new UpdateManager();
|
|
264
331
|
await updater.checkAndPerformUpdate(true);
|
|
265
332
|
break;
|
|
333
|
+
case '/skills':
|
|
334
|
+
await this.handleSkillsCommand(args);
|
|
335
|
+
break;
|
|
266
336
|
default:
|
|
267
337
|
console.log(chalk.red(`Unknown command: ${command}`));
|
|
268
338
|
}
|
|
@@ -270,6 +340,7 @@ export class ReplManager {
|
|
|
270
340
|
|
|
271
341
|
private async handleChat(input: string) {
|
|
272
342
|
const context = this.contextManager.getContextString();
|
|
343
|
+
const skillsContext = this.skillsManager.getSkillsContext();
|
|
273
344
|
let fullInput = input;
|
|
274
345
|
|
|
275
346
|
let modeInstruction = '';
|
|
@@ -281,6 +352,11 @@ export class ReplManager {
|
|
|
281
352
|
|
|
282
353
|
fullInput = `${input}${modeInstruction}`;
|
|
283
354
|
|
|
355
|
+
// Add skills context if available
|
|
356
|
+
if (skillsContext) {
|
|
357
|
+
fullInput = `${skillsContext}\n\n${fullInput}`;
|
|
358
|
+
}
|
|
359
|
+
|
|
284
360
|
if (context) {
|
|
285
361
|
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
286
362
|
}
|
|
@@ -343,7 +419,8 @@ export class ReplManager {
|
|
|
343
419
|
console.log(chalk.dim(` [Action] ${toolName}(${displayArgs})`));
|
|
344
420
|
|
|
345
421
|
// Safety check for write_file
|
|
346
|
-
if
|
|
422
|
+
// Skip confirmation if tool is allowed by active skill
|
|
423
|
+
if (toolName === 'write_file' && !this.isToolAllowedBySkill('Write')) {
|
|
347
424
|
// Pause cancellation listener during user interaction
|
|
348
425
|
if (process.stdin.isTTY) {
|
|
349
426
|
process.stdin.removeListener('keypress', keyListener);
|
|
@@ -863,6 +940,100 @@ export class ReplManager {
|
|
|
863
940
|
}
|
|
864
941
|
}
|
|
865
942
|
|
|
943
|
+
private async handleSkillsCommand(args: string[]) {
|
|
944
|
+
const { SkillCreator, validateSkills } = await import('../skills/SkillCreator');
|
|
945
|
+
|
|
946
|
+
if (args.length < 1) {
|
|
947
|
+
// Show skills list by default
|
|
948
|
+
await this.handleSkillsCommand(['list']);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const action = args[0];
|
|
953
|
+
|
|
954
|
+
switch (action) {
|
|
955
|
+
case 'list':
|
|
956
|
+
await this.handleSkillsList();
|
|
957
|
+
break;
|
|
958
|
+
case 'show':
|
|
959
|
+
if (args.length < 2) {
|
|
960
|
+
console.log(chalk.red('Usage: /skills show <name>'));
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
await this.handleSkillsShow(args[1]);
|
|
964
|
+
break;
|
|
965
|
+
case 'create':
|
|
966
|
+
const creator = new SkillCreator(this.skillsManager);
|
|
967
|
+
await creator.run(args[1]);
|
|
968
|
+
// Re-discover skills after creation
|
|
969
|
+
await this.skillsManager.discoverSkills();
|
|
970
|
+
break;
|
|
971
|
+
case 'validate':
|
|
972
|
+
await validateSkills(this.skillsManager);
|
|
973
|
+
break;
|
|
974
|
+
default:
|
|
975
|
+
console.log(chalk.red(`Unknown skills action: ${action}`));
|
|
976
|
+
console.log(chalk.yellow('Available actions: list, show, create, validate'));
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private async handleSkillsList(): Promise<void> {
|
|
981
|
+
const skills = this.skillsManager.getAllSkills();
|
|
982
|
+
|
|
983
|
+
if (skills.length === 0) {
|
|
984
|
+
console.log(chalk.yellow('No skills available.'));
|
|
985
|
+
console.log(chalk.dim('Create skills with: /skills create'));
|
|
986
|
+
console.log(chalk.dim('Add skills to: ~/.mentis/skills/ or .mentis/skills/'));
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
console.log(chalk.cyan(`\nAvailable Skills (${skills.length}):\n`));
|
|
991
|
+
|
|
992
|
+
for (const skill of skills) {
|
|
993
|
+
const statusIcon = skill.isValid ? '✓' : '✗';
|
|
994
|
+
const typeLabel = skill.type === 'personal' ? 'Personal' : 'Project';
|
|
995
|
+
|
|
996
|
+
console.log(`${statusIcon} ${chalk.bold(skill.name)} (${typeLabel})`);
|
|
997
|
+
console.log(` ${skill.description}`);
|
|
998
|
+
|
|
999
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
1000
|
+
console.log(chalk.dim(` Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (!skill.isValid && skill.errors) {
|
|
1004
|
+
console.log(chalk.red(` Errors: ${skill.errors.join(', ')}`));
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
console.log('');
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
private async handleSkillsShow(name: string): Promise<void> {
|
|
1012
|
+
const skill = await this.skillsManager.loadFullSkill(name);
|
|
1013
|
+
|
|
1014
|
+
if (!skill) {
|
|
1015
|
+
console.log(chalk.red(`Skill "${name}" not found.`));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
console.log(chalk.cyan(`\n# ${skill.name}\n`));
|
|
1020
|
+
console.log(chalk.dim(`Type: ${skill.type}`));
|
|
1021
|
+
console.log(chalk.dim(`Path: ${skill.path}`));
|
|
1022
|
+
|
|
1023
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
1024
|
+
console.log(chalk.dim(`Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
console.log('');
|
|
1028
|
+
console.log(skill.content || 'No content available');
|
|
1029
|
+
|
|
1030
|
+
// List supporting files
|
|
1031
|
+
const files = this.skillsManager.listSkillFiles(name);
|
|
1032
|
+
if (files.length > 0) {
|
|
1033
|
+
console.log(chalk.dim(`\nSupporting files: ${files.join(', ')}`));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
866
1037
|
private estimateCost(input: number, output: number): number {
|
|
867
1038
|
const config = this.configManager.getConfig();
|
|
868
1039
|
const provider = config.defaultProvider;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoadSkillTool - Tool for model-invoked skill loading
|
|
3
|
+
* The model can call this tool when it determines a skill is relevant to the current task
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Tool } from '../tools/Tool';
|
|
7
|
+
import { SkillsManager, Skill } from './SkillsManager';
|
|
8
|
+
|
|
9
|
+
interface LoadSkillArgs {
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type SkillLoadedCallback = (skill: Skill | null) => void;
|
|
14
|
+
|
|
15
|
+
export class LoadSkillTool implements Tool {
|
|
16
|
+
name = 'load_skill';
|
|
17
|
+
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" })';
|
|
18
|
+
parameters = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
name: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'The name of the skill to load (e.g., "commit-helper", "pdf-processing")'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: ['name']
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
private skillsManager: SkillsManager;
|
|
30
|
+
private onSkillLoaded?: SkillLoadedCallback;
|
|
31
|
+
|
|
32
|
+
constructor(skillsManager: SkillsManager, onSkillLoaded?: SkillLoadedCallback) {
|
|
33
|
+
this.skillsManager = skillsManager;
|
|
34
|
+
this.onSkillLoaded = onSkillLoaded;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async execute(args: LoadSkillArgs): Promise<string> {
|
|
38
|
+
const { name } = args;
|
|
39
|
+
|
|
40
|
+
if (!name) {
|
|
41
|
+
return 'Error: Skill name is required';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const skill = this.skillsManager.getSkill(name);
|
|
45
|
+
if (!skill) {
|
|
46
|
+
const availableSkills = this.skillsManager.getAllSkills().map(s => s.name).join(', ');
|
|
47
|
+
return `Error: Skill "${name}" not found. Available skills: ${availableSkills || 'none'}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Load full skill content
|
|
51
|
+
const fullSkill = await this.skillsManager.loadFullSkill(name);
|
|
52
|
+
if (!fullSkill || !fullSkill.content) {
|
|
53
|
+
return `Error: Failed to load content for skill "${name}"`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Notify callback that skill was loaded
|
|
57
|
+
if (this.onSkillLoaded) {
|
|
58
|
+
this.onSkillLoaded(fullSkill);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Format response with skill content
|
|
62
|
+
let response = `# Loaded Skill: ${skill.name}\n\n`;
|
|
63
|
+
response += `**Type**: ${skill.type}\n`;
|
|
64
|
+
response += `**Description**: ${skill.description}\n`;
|
|
65
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
66
|
+
response += `**Allowed Tools**: ${skill.allowedTools.join(', ')}\n`;
|
|
67
|
+
}
|
|
68
|
+
response += `\n---\n\n`;
|
|
69
|
+
response += fullSkill.content;
|
|
70
|
+
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ListSkillsTool - Tool for listing available skills
|
|
77
|
+
*/
|
|
78
|
+
export class ListSkillsTool implements Tool {
|
|
79
|
+
name = 'list_skills';
|
|
80
|
+
description = 'List all available skills with their descriptions. Use this to see what skills are available.';
|
|
81
|
+
parameters = {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {},
|
|
84
|
+
required: []
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
private skillsManager: SkillsManager;
|
|
88
|
+
|
|
89
|
+
constructor(skillsManager: SkillsManager) {
|
|
90
|
+
this.skillsManager = skillsManager;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async execute(): Promise<string> {
|
|
94
|
+
const skills = this.skillsManager.getAllSkills();
|
|
95
|
+
|
|
96
|
+
if (skills.length === 0) {
|
|
97
|
+
return 'No skills available. Add skills to ~/.mentis/skills/ or .mentis/skills/';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let response = `# Available Skills (${skills.length})\n\n`;
|
|
101
|
+
|
|
102
|
+
for (const skill of skills) {
|
|
103
|
+
const statusIcon = skill.isValid ? '✓' : '✗';
|
|
104
|
+
response += `**${statusIcon} ${skill.name}** (${skill.type})\n`;
|
|
105
|
+
response += ` ${skill.description}\n`;
|
|
106
|
+
|
|
107
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
108
|
+
response += ` Allowed tools: ${skill.allowedTools.join(', ')}\n`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!skill.isValid && skill.errors) {
|
|
112
|
+
response += ` Errors: ${skill.errors.join(', ')}\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
response += '\n';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return response;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* ReadSkillFileTool - Tool for reading supporting files within a skill
|
|
124
|
+
* Used for progressive disclosure of skill resources
|
|
125
|
+
*/
|
|
126
|
+
export class ReadSkillFileTool implements Tool {
|
|
127
|
+
name = 'read_skill_file';
|
|
128
|
+
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" })';
|
|
129
|
+
parameters = {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
skill: {
|
|
133
|
+
type: 'string',
|
|
134
|
+
description: 'The name of the skill'
|
|
135
|
+
},
|
|
136
|
+
file: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
description: 'The filename within the skill directory (e.g., "reference.md", "examples.md")'
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
required: ['skill', 'file']
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
private skillsManager: SkillsManager;
|
|
145
|
+
|
|
146
|
+
constructor(skillsManager: SkillsManager) {
|
|
147
|
+
this.skillsManager = skillsManager;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async execute(args: { skill: string; file: string }): Promise<string> {
|
|
151
|
+
const { skill, file } = args;
|
|
152
|
+
|
|
153
|
+
if (!skill || !file) {
|
|
154
|
+
return 'Error: Both skill and file parameters are required';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const content = this.skillsManager.readSkillFile(skill, file);
|
|
158
|
+
if (content === null) {
|
|
159
|
+
const availableFiles = this.skillsManager.listSkillFiles(skill);
|
|
160
|
+
if (availableFiles.length === 0) {
|
|
161
|
+
return `Error: Skill "${skill}" has no supporting files`;
|
|
162
|
+
}
|
|
163
|
+
return `Error: File "${file}" not found in skill "${skill}". Available files: ${availableFiles.join(', ')}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return content;
|
|
167
|
+
}
|
|
168
|
+
}
|