@indiccoder/mentis-cli 1.0.5 → 1.0.8
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 +18 -14
- package/dist/ui/UIManager.js +46 -8
- package/dist/utils/UpdateManager.js +81 -0
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/repl/ReplManager.ts +19 -14
- package/src/ui/UIManager.ts +61 -15
- package/src/utils/UpdateManager.ts +83 -0
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
|
}
|
package/dist/repl/ReplManager.js
CHANGED
|
@@ -123,27 +123,25 @@ class ReplManager {
|
|
|
123
123
|
// console.log(chalk.dim(`Initialized ${provider} client with model ${model}`));
|
|
124
124
|
}
|
|
125
125
|
async start() {
|
|
126
|
-
UIManager_1.UIManager.
|
|
127
|
-
|
|
126
|
+
UIManager_1.UIManager.renderDashboard({
|
|
127
|
+
model: this.currentModelName,
|
|
128
|
+
mode: this.mode,
|
|
129
|
+
cwd: process.cwd()
|
|
130
|
+
});
|
|
128
131
|
// Load History
|
|
129
132
|
let commandHistory = [];
|
|
130
133
|
if (fs.existsSync(HISTORY_FILE)) {
|
|
131
134
|
try {
|
|
132
|
-
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
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.
|
|
135
|
+
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
137
136
|
}
|
|
138
137
|
catch (e) { }
|
|
139
138
|
}
|
|
140
139
|
while (true) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
const promptText = `${modeLabel}${chalk_1.default.dim(modelInfo)} ${chalk_1.default.cyan('>')}`;
|
|
140
|
+
// Minimalist Separator
|
|
141
|
+
console.log(chalk_1.default.gray('────────────────────────────────────────────────────────────────────────────────'));
|
|
142
|
+
// Hint (Claude style puts it below, we put it above for standard terminal compatibility)
|
|
143
|
+
console.log(chalk_1.default.dim(' ? for shortcuts'));
|
|
144
|
+
const promptText = `> `; // Clean prompt
|
|
147
145
|
// Use readline for basic input to support history
|
|
148
146
|
const answer = await new Promise((resolve) => {
|
|
149
147
|
const rl = readline.createInterface({
|
|
@@ -151,7 +149,7 @@ class ReplManager {
|
|
|
151
149
|
output: process.stdout,
|
|
152
150
|
history: commandHistory,
|
|
153
151
|
historySize: 1000,
|
|
154
|
-
prompt: promptText
|
|
152
|
+
prompt: promptText
|
|
155
153
|
});
|
|
156
154
|
rl.prompt();
|
|
157
155
|
rl.on('line', (line) => {
|
|
@@ -193,6 +191,7 @@ class ReplManager {
|
|
|
193
191
|
console.log(' /help - Show this help message');
|
|
194
192
|
console.log(' /clear - Clear chat history');
|
|
195
193
|
console.log(' /exit - Exit the application');
|
|
194
|
+
console.log(' /update - Check for and install updates');
|
|
196
195
|
console.log(' /config - Configure settings');
|
|
197
196
|
console.log(' /add <file> - Add file to context');
|
|
198
197
|
console.log(' /drop <file> - Remove file from context');
|
|
@@ -274,6 +273,11 @@ class ReplManager {
|
|
|
274
273
|
console.log(chalk_1.default.green('Session saved. Goodbye!'));
|
|
275
274
|
process.exit(0);
|
|
276
275
|
break;
|
|
276
|
+
case '/update':
|
|
277
|
+
const UpdateManager = require('../utils/UpdateManager').UpdateManager;
|
|
278
|
+
const updater = new UpdateManager();
|
|
279
|
+
await updater.checkAndPerformUpdate(true);
|
|
280
|
+
break;
|
|
277
281
|
default:
|
|
278
282
|
console.log(chalk_1.default.red(`Unknown command: ${command}`));
|
|
279
283
|
}
|
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;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
import { ReplManager } from './repl/ReplManager';
|
|
3
3
|
|
|
4
4
|
async function main() {
|
|
5
|
+
if (process.argv.includes('update')) {
|
|
6
|
+
const { UpdateManager } = require('./utils/UpdateManager');
|
|
7
|
+
const updater = new UpdateManager();
|
|
8
|
+
await updater.checkAndPerformUpdate(true);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
const repl = new ReplManager();
|
|
6
13
|
await repl.start();
|
|
7
14
|
}
|
package/src/repl/ReplManager.ts
CHANGED
|
@@ -99,29 +99,28 @@ export class ReplManager {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
public async start() {
|
|
102
|
-
UIManager.
|
|
103
|
-
|
|
102
|
+
UIManager.renderDashboard({
|
|
103
|
+
model: this.currentModelName,
|
|
104
|
+
mode: this.mode,
|
|
105
|
+
cwd: process.cwd()
|
|
106
|
+
});
|
|
104
107
|
|
|
105
108
|
// Load History
|
|
106
109
|
let commandHistory: string[] = [];
|
|
107
110
|
if (fs.existsSync(HISTORY_FILE)) {
|
|
108
111
|
try {
|
|
109
|
-
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
110
|
-
// readline.history is [newest, ..., oldest]
|
|
111
|
-
// If I read from file where newest is at bottom (standard append), I need to reverse it.
|
|
112
|
-
// Let's assume standard file: line 1 (old), line 2 (new).
|
|
113
|
-
// So split -> reverse -> history.
|
|
112
|
+
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
114
113
|
} catch (e) { }
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
while (true) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// Minimalist Separator
|
|
118
|
+
console.log(chalk.gray('────────────────────────────────────────────────────────────────────────────────'));
|
|
119
|
+
|
|
120
|
+
// Hint (Claude style puts it below, we put it above for standard terminal compatibility)
|
|
121
|
+
console.log(chalk.dim(' ? for shortcuts'));
|
|
121
122
|
|
|
122
|
-
const
|
|
123
|
-
const modelInfo = this.currentModelName ? ` (${this.currentModelName})` : '';
|
|
124
|
-
const promptText = `${modeLabel}${chalk.dim(modelInfo)} ${chalk.cyan('>')}`;
|
|
123
|
+
const promptText = `> `; // Clean prompt
|
|
125
124
|
|
|
126
125
|
// Use readline for basic input to support history
|
|
127
126
|
const answer = await new Promise<string>((resolve) => {
|
|
@@ -130,7 +129,7 @@ export class ReplManager {
|
|
|
130
129
|
output: process.stdout,
|
|
131
130
|
history: commandHistory,
|
|
132
131
|
historySize: 1000,
|
|
133
|
-
prompt: promptText
|
|
132
|
+
prompt: promptText
|
|
134
133
|
});
|
|
135
134
|
|
|
136
135
|
rl.prompt();
|
|
@@ -179,6 +178,7 @@ export class ReplManager {
|
|
|
179
178
|
console.log(' /help - Show this help message');
|
|
180
179
|
console.log(' /clear - Clear chat history');
|
|
181
180
|
console.log(' /exit - Exit the application');
|
|
181
|
+
console.log(' /update - Check for and install updates');
|
|
182
182
|
console.log(' /config - Configure settings');
|
|
183
183
|
console.log(' /add <file> - Add file to context');
|
|
184
184
|
console.log(' /drop <file> - Remove file from context');
|
|
@@ -258,6 +258,11 @@ export class ReplManager {
|
|
|
258
258
|
console.log(chalk.green('Session saved. Goodbye!'));
|
|
259
259
|
process.exit(0);
|
|
260
260
|
break;
|
|
261
|
+
case '/update':
|
|
262
|
+
const UpdateManager = require('../utils/UpdateManager').UpdateManager;
|
|
263
|
+
const updater = new UpdateManager();
|
|
264
|
+
await updater.checkAndPerformUpdate(true);
|
|
265
|
+
break;
|
|
261
266
|
default:
|
|
262
267
|
console.log(chalk.red(`Unknown command: ${command}`));
|
|
263
268
|
}
|
package/src/ui/UIManager.ts
CHANGED
|
@@ -7,34 +7,80 @@ export class UIManager {
|
|
|
7
7
|
public static displayLogo() {
|
|
8
8
|
console.clear();
|
|
9
9
|
const logoText = figlet.textSync('MENTIS', {
|
|
10
|
-
font: 'ANSI Shadow',
|
|
10
|
+
font: 'ANSI Shadow',
|
|
11
11
|
horizontalLayout: 'default',
|
|
12
12
|
verticalLayout: 'default',
|
|
13
13
|
width: 100,
|
|
14
14
|
whitespaceBreak: true,
|
|
15
15
|
});
|
|
16
16
|
console.log(gradient.pastel.multiline(logoText));
|
|
17
|
-
console.log(chalk.gray(' v1.0.
|
|
17
|
+
console.log(chalk.gray(' v1.0.5 - AI Coding Agent'));
|
|
18
18
|
console.log('');
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
public static
|
|
21
|
+
public static renderDashboard(config: { model: string, mode: string, cwd: string }) {
|
|
22
|
+
const { model, cwd } = config;
|
|
23
|
+
const version = 'v1.0.8';
|
|
24
|
+
|
|
25
|
+
// Layout: Left (Status/Welcome) | Right (Tips/Activity)
|
|
26
|
+
// Total width ~80 chars.
|
|
27
|
+
// Left ~45, Right ~30.
|
|
28
|
+
|
|
29
|
+
const pad = (str: string, width: number) => str + ' '.repeat(Math.max(0, width - str.length));
|
|
30
|
+
|
|
31
|
+
const logo = gradient.pastel.multiline(figlet.textSync('MENTIS', { font: 'Small' }));
|
|
32
|
+
const logoLines = logo.split('\n');
|
|
33
|
+
|
|
34
|
+
// Tips Column
|
|
35
|
+
const tips = [
|
|
36
|
+
chalk.bold('Tips for getting started'),
|
|
37
|
+
chalk.dim('Run /init to scaffold project'),
|
|
38
|
+
chalk.dim('Run /model to switch AI'),
|
|
39
|
+
chalk.dim('Run /help for full list')
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Combine Logo (Left) and Tips (Right)
|
|
43
|
+
let body = '';
|
|
44
|
+
for (let i = 0; i < Math.max(logoLines.length, tips.length); i++) {
|
|
45
|
+
const left = logoLines[i] || ''; // Logo line
|
|
46
|
+
const right = tips[i] || ''; // Tip line
|
|
47
|
+
// Need to strip ansi to calc padding? simple padding might break with ansi.
|
|
48
|
+
// Let's just create two distinct blocks and join them?
|
|
49
|
+
// Complex with boxen.
|
|
50
|
+
// Let's stick to vertical stack if side-by-side matches ansi poorly.
|
|
51
|
+
// Actually, let's just use the previous cleaner vertical stack but wider.
|
|
52
|
+
// User liked the previous one "this is exellent", just wanted input box.
|
|
53
|
+
// So I will keep the Dashboard mostly same, maybe just widen it.
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Re-using the clean layout but ensuring no "undefined" or weird overlaps
|
|
57
|
+
const title = ` Mentis-CLI ${version} `;
|
|
58
|
+
|
|
59
|
+
const content =
|
|
60
|
+
` ${chalk.bold('Welcome back!')}\n\n` +
|
|
61
|
+
`${logo}\n\n` +
|
|
62
|
+
` ${chalk.dim('Model:')} ${chalk.cyan(model)}\n` +
|
|
63
|
+
` ${chalk.dim('Dir:')} ${chalk.dim(cwd)}\n\n` +
|
|
64
|
+
`${chalk.gray('────────────────────────────────────────────────────────────────')}\n` +
|
|
65
|
+
` ${chalk.dim('Tips: /help • /config • /mcp • Esc to cancel')}`;
|
|
66
|
+
|
|
22
67
|
console.log(
|
|
23
|
-
boxen(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
)
|
|
68
|
+
boxen(content, {
|
|
69
|
+
padding: 1,
|
|
70
|
+
margin: 0,
|
|
71
|
+
borderStyle: 'round',
|
|
72
|
+
borderColor: 'cyan',
|
|
73
|
+
title: title,
|
|
74
|
+
titleAlignment: 'left',
|
|
75
|
+
dimBorder: true,
|
|
76
|
+
width: 80
|
|
77
|
+
})
|
|
35
78
|
);
|
|
79
|
+
console.log('');
|
|
36
80
|
}
|
|
37
81
|
|
|
82
|
+
|
|
83
|
+
|
|
38
84
|
public static printSeparator() {
|
|
39
85
|
console.log(chalk.gray('──────────────────────────────────────────────────'));
|
|
40
86
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
export class UpdateManager {
|
|
12
|
+
private packageName: string;
|
|
13
|
+
private currentVersion: string;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
const packageJsonPath = path.join(__dirname, '../../package.json');
|
|
17
|
+
try {
|
|
18
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
19
|
+
this.packageName = packageJson.name;
|
|
20
|
+
this.currentVersion = packageJson.version;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
// Fallback if running from a context where package.json isn't found easily (e.g. global install oddities)
|
|
23
|
+
// But usually this works relative to dist/utils/
|
|
24
|
+
this.packageName = '@indiccoder/mentis-cli';
|
|
25
|
+
this.currentVersion = '0.0.0';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public async checkAndPerformUpdate(interactive: boolean = true) {
|
|
30
|
+
const spinner = ora('Checking for updates...').start();
|
|
31
|
+
|
|
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
|
+
|
|
37
|
+
if (latestVersion === this.currentVersion) {
|
|
38
|
+
spinner.succeed(chalk.green(`You are on the latest version (${this.currentVersion}).`));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
spinner.info(chalk.blue(`Update available: ${this.currentVersion} -> ${chalk.bold(latestVersion)}`));
|
|
43
|
+
|
|
44
|
+
if (!interactive) {
|
|
45
|
+
// If running in non-interactive mode (e.g. auto-check prompt), maybe just log it.
|
|
46
|
+
// But for explicit 'update' command, we usually assume interactive or force.
|
|
47
|
+
console.log(chalk.yellow(`Run 'mentis update' or '/update' inside the tool to upgrade.`));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { confirm } = await inquirer.prompt([{
|
|
52
|
+
type: 'confirm',
|
|
53
|
+
name: 'confirm',
|
|
54
|
+
message: `Do you want to install v${latestVersion} now?`,
|
|
55
|
+
default: true
|
|
56
|
+
}]);
|
|
57
|
+
|
|
58
|
+
if (confirm) {
|
|
59
|
+
await this.installUpdate(latestVersion);
|
|
60
|
+
} else {
|
|
61
|
+
console.log(chalk.yellow('Update skipped.'));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} catch (error: any) {
|
|
65
|
+
spinner.fail(chalk.red('Failed to check for updates.'));
|
|
66
|
+
if (process.env.DEBUG) console.error(error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private async installUpdate(version: string) {
|
|
71
|
+
const spinner = ora(`Installing ${this.packageName}@${version}...`).start();
|
|
72
|
+
try {
|
|
73
|
+
await execAsync(`npm install -g ${this.packageName}@latest`);
|
|
74
|
+
spinner.succeed(chalk.green('Update completed successfully!'));
|
|
75
|
+
console.log(chalk.cyan('Please restart Mentis to use the new version.'));
|
|
76
|
+
process.exit(0);
|
|
77
|
+
} catch (error: any) {
|
|
78
|
+
spinner.fail(chalk.red('Update failed.'));
|
|
79
|
+
console.error(chalk.red('Error details:'), error.message);
|
|
80
|
+
console.log(chalk.yellow(`Try running: npm install -g ${this.packageName}@latest`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|