@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 +6 -0
- package/dist/repl/ReplManager.js +160 -17
- package/dist/skills/LoadSkillTool.js +133 -0
- package/dist/skills/Skill.js +6 -0
- package/dist/skills/SkillCreator.js +247 -0
- package/dist/skills/SkillsManager.js +337 -0
- package/dist/ui/UIManager.js +46 -8
- package/dist/utils/UpdateManager.js +81 -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/index.ts +7 -0
- package/src/repl/ReplManager.ts +193 -17
- package/src/skills/LoadSkillTool.ts +168 -0
- package/src/skills/Skill.ts +51 -0
- package/src/skills/SkillCreator.ts +237 -0
- package/src/skills/SkillsManager.ts +354 -0
- package/src/ui/UIManager.ts +61 -15
- package/src/utils/UpdateManager.ts +83 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SkillsManager - Core module for Agent Skills system
|
|
4
|
+
* Handles discovery, loading, and validation of skills
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.SkillsManager = void 0;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const fast_glob_1 = require("fast-glob");
|
|
48
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
49
|
+
class SkillsManager {
|
|
50
|
+
constructor(cwd = process.cwd()) {
|
|
51
|
+
this.skills = new Map();
|
|
52
|
+
this.personalSkillsDir = path.join(os.homedir(), '.mentis', 'skills');
|
|
53
|
+
this.projectSkillsDir = path.join(cwd, '.mentis', 'skills');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Discover all skills from configured directories
|
|
57
|
+
*/
|
|
58
|
+
async discoverSkills(options = {}) {
|
|
59
|
+
const { includePersonal = true, includeProject = true, includePlugin = false // Plugin skills not implemented yet
|
|
60
|
+
} = options;
|
|
61
|
+
const discovered = [];
|
|
62
|
+
if (includePersonal) {
|
|
63
|
+
discovered.push(...await this.discoverSkillsInDirectory(this.personalSkillsDir, 'personal'));
|
|
64
|
+
}
|
|
65
|
+
if (includeProject) {
|
|
66
|
+
discovered.push(...await this.discoverSkillsInDirectory(this.projectSkillsDir, 'project'));
|
|
67
|
+
}
|
|
68
|
+
// Store skills in map for quick lookup
|
|
69
|
+
for (const skill of discovered) {
|
|
70
|
+
this.skills.set(skill.name, skill);
|
|
71
|
+
}
|
|
72
|
+
return Array.from(this.skills.values());
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Discover skills in a specific directory
|
|
76
|
+
*/
|
|
77
|
+
async discoverSkillsInDirectory(dir, type) {
|
|
78
|
+
if (!fs.existsSync(dir)) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const skills = [];
|
|
82
|
+
try {
|
|
83
|
+
// Find all SKILL.md files in subdirectories
|
|
84
|
+
const skillFiles = await (0, fast_glob_1.glob)('**/SKILL.md', {
|
|
85
|
+
cwd: dir,
|
|
86
|
+
absolute: true,
|
|
87
|
+
onlyFiles: true
|
|
88
|
+
});
|
|
89
|
+
for (const skillFile of skillFiles) {
|
|
90
|
+
const skillDir = path.dirname(skillFile);
|
|
91
|
+
const skillName = path.basename(skillDir);
|
|
92
|
+
const skill = await this.loadSkillMetadata(skillFile, skillDir, type);
|
|
93
|
+
if (skill) {
|
|
94
|
+
skills.push(skill);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(`Error discovering skills in ${dir}: ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
return skills;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Load only the metadata (YAML frontmatter) from a SKILL.md file
|
|
105
|
+
* This is used for progressive disclosure - only name/description loaded at startup
|
|
106
|
+
*/
|
|
107
|
+
async loadSkillMetadata(skillPath, skillDir, type) {
|
|
108
|
+
try {
|
|
109
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
110
|
+
const frontmatter = this.extractFrontmatter(content);
|
|
111
|
+
if (!frontmatter) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
// Convert SkillFrontmatter to SkillMetadata for validation
|
|
115
|
+
const metadata = {
|
|
116
|
+
name: frontmatter.name,
|
|
117
|
+
description: frontmatter.description,
|
|
118
|
+
allowedTools: frontmatter['allowed-tools']
|
|
119
|
+
};
|
|
120
|
+
const validation = this.validateSkillMetadata(metadata);
|
|
121
|
+
const skill = {
|
|
122
|
+
name: frontmatter.name,
|
|
123
|
+
description: frontmatter.description,
|
|
124
|
+
allowedTools: frontmatter['allowed-tools'],
|
|
125
|
+
path: skillPath,
|
|
126
|
+
directory: skillDir,
|
|
127
|
+
type,
|
|
128
|
+
isValid: validation.isValid,
|
|
129
|
+
errors: validation.errors.length > 0 ? validation.errors : undefined
|
|
130
|
+
};
|
|
131
|
+
return skill;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error(`Error loading skill metadata from ${skillPath}: ${error.message}`);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Load the full content of a skill (for progressive disclosure)
|
|
140
|
+
*/
|
|
141
|
+
async loadFullSkill(name) {
|
|
142
|
+
const skill = this.skills.get(name);
|
|
143
|
+
if (!skill) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const content = fs.readFileSync(skill.path, 'utf-8');
|
|
148
|
+
skill.content = content;
|
|
149
|
+
return skill;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.error(`Error loading full skill ${name}: ${error.message}`);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Extract YAML frontmatter from markdown content
|
|
158
|
+
*/
|
|
159
|
+
extractFrontmatter(content) {
|
|
160
|
+
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
161
|
+
const match = content.match(frontmatterRegex);
|
|
162
|
+
if (!match) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const parsed = yaml_1.default.parse(match[1]);
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Validate skill metadata according to Claude Code spec
|
|
175
|
+
*/
|
|
176
|
+
validateSkillMetadata(metadata) {
|
|
177
|
+
const errors = [];
|
|
178
|
+
const warnings = [];
|
|
179
|
+
// Check required fields
|
|
180
|
+
if (!metadata.name) {
|
|
181
|
+
errors.push('Missing required field: name');
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Name validation: lowercase letters, numbers, hyphens only, max 64 chars
|
|
185
|
+
const nameRegex = /^[a-z0-9-]+$/;
|
|
186
|
+
if (!nameRegex.test(metadata.name)) {
|
|
187
|
+
errors.push('Name must contain only lowercase letters, numbers, and hyphens');
|
|
188
|
+
}
|
|
189
|
+
if (metadata.name.length > 64) {
|
|
190
|
+
errors.push('Name must be 64 characters or less');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!metadata.description) {
|
|
194
|
+
errors.push('Missing required field: description');
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
if (metadata.description.length > 1024) {
|
|
198
|
+
errors.push('Description must be 1024 characters or less');
|
|
199
|
+
}
|
|
200
|
+
// Check if description mentions when to use
|
|
201
|
+
if (!metadata.description.toLowerCase().includes('use when') &&
|
|
202
|
+
!metadata.description.toLowerCase().includes('use for')) {
|
|
203
|
+
warnings.push('Description should include when to use this skill (e.g., "Use when...")');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Validate allowed-tools if present
|
|
207
|
+
if (metadata.allowedTools) {
|
|
208
|
+
const validTools = [
|
|
209
|
+
'Read', 'Write', 'Edit', 'Grep', 'Glob', 'Bash', 'WebSearch',
|
|
210
|
+
'GitStatus', 'GitDiff', 'GitCommit', 'GitPush', 'GitPull',
|
|
211
|
+
'ListDir', 'SearchFile', 'RunShell'
|
|
212
|
+
];
|
|
213
|
+
const invalidTools = metadata.allowedTools.filter(t => !validTools.includes(t));
|
|
214
|
+
if (invalidTools.length > 0) {
|
|
215
|
+
warnings.push(`Unknown tools in allowed-tools: ${invalidTools.join(', ')}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
isValid: errors.length === 0,
|
|
220
|
+
errors,
|
|
221
|
+
warnings
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get skill by name
|
|
226
|
+
*/
|
|
227
|
+
getSkill(name) {
|
|
228
|
+
return this.skills.get(name);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get all skills
|
|
232
|
+
*/
|
|
233
|
+
getAllSkills() {
|
|
234
|
+
return Array.from(this.skills.values());
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get skills formatted for model context injection
|
|
238
|
+
* This provides only metadata for progressive disclosure
|
|
239
|
+
*/
|
|
240
|
+
getSkillsContext() {
|
|
241
|
+
const skills = this.getAllSkills().filter(s => s.isValid);
|
|
242
|
+
if (skills.length === 0) {
|
|
243
|
+
return '';
|
|
244
|
+
}
|
|
245
|
+
const context = skills.map(skill => {
|
|
246
|
+
return `- ${skill.name}: ${skill.description}`;
|
|
247
|
+
}).join('\n');
|
|
248
|
+
return `Available Skills:\n${context}`;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get skills as SkillContext array
|
|
252
|
+
*/
|
|
253
|
+
getSkillsContextArray() {
|
|
254
|
+
return this.getAllSkills()
|
|
255
|
+
.filter(s => s.isValid)
|
|
256
|
+
.map(s => ({
|
|
257
|
+
name: s.name,
|
|
258
|
+
description: s.description
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Validate all loaded skills
|
|
263
|
+
*/
|
|
264
|
+
validateAllSkills() {
|
|
265
|
+
const results = new Map();
|
|
266
|
+
for (const [name, skill] of this.skills) {
|
|
267
|
+
const metadata = {
|
|
268
|
+
name: skill.name,
|
|
269
|
+
description: skill.description,
|
|
270
|
+
allowedTools: skill.allowedTools
|
|
271
|
+
};
|
|
272
|
+
results.set(name, this.validateSkillMetadata(metadata));
|
|
273
|
+
}
|
|
274
|
+
return results;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Read a supporting file from a skill directory
|
|
278
|
+
* Used for progressive disclosure of skill resources
|
|
279
|
+
*/
|
|
280
|
+
readSkillFile(skillName, fileName) {
|
|
281
|
+
const skill = this.skills.get(skillName);
|
|
282
|
+
if (!skill) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const filePath = path.join(skill.directory, fileName);
|
|
286
|
+
if (!fs.existsSync(filePath)) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* List supporting files in a skill directory
|
|
298
|
+
*/
|
|
299
|
+
listSkillFiles(skillName) {
|
|
300
|
+
const skill = this.skills.get(skillName);
|
|
301
|
+
if (!skill) {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
if (!fs.existsSync(skill.directory)) {
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const files = fs.readdirSync(skill.directory);
|
|
309
|
+
// Exclude SKILL.md as it's the main file
|
|
310
|
+
return files.filter(f => f !== 'SKILL.md');
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Create skills directories if they don't exist
|
|
318
|
+
*/
|
|
319
|
+
ensureDirectoriesExist() {
|
|
320
|
+
if (!fs.existsSync(this.personalSkillsDir)) {
|
|
321
|
+
fs.mkdirSync(this.personalSkillsDir, { recursive: true });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get personal skills directory path
|
|
326
|
+
*/
|
|
327
|
+
getPersonalSkillsDir() {
|
|
328
|
+
return this.personalSkillsDir;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get project skills directory path
|
|
332
|
+
*/
|
|
333
|
+
getProjectSkillsDir() {
|
|
334
|
+
return this.projectSkillsDir;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
exports.SkillsManager = SkillsManager;
|
package/dist/ui/UIManager.js
CHANGED
|
@@ -12,26 +12,64 @@ class UIManager {
|
|
|
12
12
|
static displayLogo() {
|
|
13
13
|
console.clear();
|
|
14
14
|
const logoText = figlet_1.default.textSync('MENTIS', {
|
|
15
|
-
font: 'ANSI Shadow',
|
|
15
|
+
font: 'ANSI Shadow',
|
|
16
16
|
horizontalLayout: 'default',
|
|
17
17
|
verticalLayout: 'default',
|
|
18
18
|
width: 100,
|
|
19
19
|
whitespaceBreak: true,
|
|
20
20
|
});
|
|
21
21
|
console.log(gradient_string_1.default.pastel.multiline(logoText));
|
|
22
|
-
console.log(chalk_1.default.gray(' v1.0.
|
|
22
|
+
console.log(chalk_1.default.gray(' v1.0.5 - AI Coding Agent'));
|
|
23
23
|
console.log('');
|
|
24
24
|
}
|
|
25
|
-
static
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
static renderDashboard(config) {
|
|
26
|
+
const { model, cwd } = config;
|
|
27
|
+
const version = 'v1.0.8';
|
|
28
|
+
// Layout: Left (Status/Welcome) | Right (Tips/Activity)
|
|
29
|
+
// Total width ~80 chars.
|
|
30
|
+
// Left ~45, Right ~30.
|
|
31
|
+
const pad = (str, width) => str + ' '.repeat(Math.max(0, width - str.length));
|
|
32
|
+
const logo = gradient_string_1.default.pastel.multiline(figlet_1.default.textSync('MENTIS', { font: 'Small' }));
|
|
33
|
+
const logoLines = logo.split('\n');
|
|
34
|
+
// Tips Column
|
|
35
|
+
const tips = [
|
|
36
|
+
chalk_1.default.bold('Tips for getting started'),
|
|
37
|
+
chalk_1.default.dim('Run /init to scaffold project'),
|
|
38
|
+
chalk_1.default.dim('Run /model to switch AI'),
|
|
39
|
+
chalk_1.default.dim('Run /help for full list')
|
|
40
|
+
];
|
|
41
|
+
// Combine Logo (Left) and Tips (Right)
|
|
42
|
+
let body = '';
|
|
43
|
+
for (let i = 0; i < Math.max(logoLines.length, tips.length); i++) {
|
|
44
|
+
const left = logoLines[i] || ''; // Logo line
|
|
45
|
+
const right = tips[i] || ''; // Tip line
|
|
46
|
+
// Need to strip ansi to calc padding? simple padding might break with ansi.
|
|
47
|
+
// Let's just create two distinct blocks and join them?
|
|
48
|
+
// Complex with boxen.
|
|
49
|
+
// Let's stick to vertical stack if side-by-side matches ansi poorly.
|
|
50
|
+
// Actually, let's just use the previous cleaner vertical stack but wider.
|
|
51
|
+
// User liked the previous one "this is exellent", just wanted input box.
|
|
52
|
+
// So I will keep the Dashboard mostly same, maybe just widen it.
|
|
53
|
+
}
|
|
54
|
+
// Re-using the clean layout but ensuring no "undefined" or weird overlaps
|
|
55
|
+
const title = ` Mentis-CLI ${version} `;
|
|
56
|
+
const content = ` ${chalk_1.default.bold('Welcome back!')}\n\n` +
|
|
57
|
+
`${logo}\n\n` +
|
|
58
|
+
` ${chalk_1.default.dim('Model:')} ${chalk_1.default.cyan(model)}\n` +
|
|
59
|
+
` ${chalk_1.default.dim('Dir:')} ${chalk_1.default.dim(cwd)}\n\n` +
|
|
60
|
+
`${chalk_1.default.gray('────────────────────────────────────────────────────────────────')}\n` +
|
|
61
|
+
` ${chalk_1.default.dim('Tips: /help • /config • /mcp • Esc to cancel')}`;
|
|
62
|
+
console.log((0, boxen_1.default)(content, {
|
|
30
63
|
padding: 1,
|
|
31
|
-
margin:
|
|
64
|
+
margin: 0,
|
|
32
65
|
borderStyle: 'round',
|
|
33
66
|
borderColor: 'cyan',
|
|
67
|
+
title: title,
|
|
68
|
+
titleAlignment: 'left',
|
|
69
|
+
dimBorder: true,
|
|
70
|
+
width: 80
|
|
34
71
|
}));
|
|
72
|
+
console.log('');
|
|
35
73
|
}
|
|
36
74
|
static printSeparator() {
|
|
37
75
|
console.log(chalk_1.default.gray('──────────────────────────────────────────────────'));
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.UpdateManager = void 0;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const util_1 = require("util");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const ora_1 = __importDefault(require("ora"));
|
|
13
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
14
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
15
|
+
class UpdateManager {
|
|
16
|
+
constructor() {
|
|
17
|
+
const packageJsonPath = path_1.default.join(__dirname, '../../package.json');
|
|
18
|
+
try {
|
|
19
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
20
|
+
this.packageName = packageJson.name;
|
|
21
|
+
this.currentVersion = packageJson.version;
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
// Fallback if running from a context where package.json isn't found easily (e.g. global install oddities)
|
|
25
|
+
// But usually this works relative to dist/utils/
|
|
26
|
+
this.packageName = '@indiccoder/mentis-cli';
|
|
27
|
+
this.currentVersion = '0.0.0';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async checkAndPerformUpdate(interactive = true) {
|
|
31
|
+
const spinner = (0, ora_1.default)('Checking for updates...').start();
|
|
32
|
+
try {
|
|
33
|
+
// Check latest version from NPM registry
|
|
34
|
+
const { stdout } = await execAsync(`npm view ${this.packageName} version`);
|
|
35
|
+
const latestVersion = stdout.trim();
|
|
36
|
+
if (latestVersion === this.currentVersion) {
|
|
37
|
+
spinner.succeed(chalk_1.default.green(`You are on the latest version (${this.currentVersion}).`));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
spinner.info(chalk_1.default.blue(`Update available: ${this.currentVersion} -> ${chalk_1.default.bold(latestVersion)}`));
|
|
41
|
+
if (!interactive) {
|
|
42
|
+
// If running in non-interactive mode (e.g. auto-check prompt), maybe just log it.
|
|
43
|
+
// But for explicit 'update' command, we usually assume interactive or force.
|
|
44
|
+
console.log(chalk_1.default.yellow(`Run 'mentis update' or '/update' inside the tool to upgrade.`));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
48
|
+
type: 'confirm',
|
|
49
|
+
name: 'confirm',
|
|
50
|
+
message: `Do you want to install v${latestVersion} now?`,
|
|
51
|
+
default: true
|
|
52
|
+
}]);
|
|
53
|
+
if (confirm) {
|
|
54
|
+
await this.installUpdate(latestVersion);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(chalk_1.default.yellow('Update skipped.'));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
spinner.fail(chalk_1.default.red('Failed to check for updates.'));
|
|
62
|
+
if (process.env.DEBUG)
|
|
63
|
+
console.error(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async installUpdate(version) {
|
|
67
|
+
const spinner = (0, ora_1.default)(`Installing ${this.packageName}@${version}...`).start();
|
|
68
|
+
try {
|
|
69
|
+
await execAsync(`npm install -g ${this.packageName}@latest`);
|
|
70
|
+
spinner.succeed(chalk_1.default.green('Update completed successfully!'));
|
|
71
|
+
console.log(chalk_1.default.cyan('Please restart Mentis to use the new version.'));
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
spinner.fail(chalk_1.default.red('Update failed.'));
|
|
76
|
+
console.error(chalk_1.default.red('Error details:'), error.message);
|
|
77
|
+
console.log(chalk_1.default.yellow(`Try running: npm install -g ${this.packageName}@latest`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.UpdateManager = UpdateManager;
|