@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 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
  }
@@ -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.displayLogo();
127
- UIManager_1.UIManager.displayWelcome();
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(); // readline expects newest first? No, newest is usually 0? Check.
133
- // readline.history is [newest, ..., oldest]
134
- // If I read from file where newest is at bottom (standard append), I need to reverse it.
135
- // Let's assume standard file: line 1 (old), line 2 (new).
136
- // So split -> reverse -> history.
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
- UIManager_1.UIManager.printSeparator();
142
- // console.log(chalk.dim(` /help for help | Model: ${chalk.cyan(this.currentModelName)}`));
143
- // Removed redundancy to keep CLI clean, prompt has info? No, prompt is minimal.
144
- const modeLabel = this.mode === 'PLAN' ? chalk_1.default.magenta('PLAN') : chalk_1.default.blue('BUILD');
145
- const modelInfo = this.currentModelName ? ` (${this.currentModelName})` : '';
146
- const promptText = `${modeLabel}${chalk_1.default.dim(modelInfo)} ${chalk_1.default.cyan('>')}`;
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
  }
@@ -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', // Use a block-like font
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.0 - AI Coding Agent'));
22
+ console.log(chalk_1.default.gray(' v1.0.5 - AI Coding Agent'));
23
23
  console.log('');
24
24
  }
25
- static displayWelcome() {
26
- console.log((0, boxen_1.default)(`${chalk_1.default.bold('Welcome to Mentis-CLI')}\n\n` +
27
- `• Type ${chalk_1.default.cyan('/help')} for commands.\n` +
28
- `• Type ${chalk_1.default.cyan('/config')} to setup your model.\n` +
29
- `• Start typing to chat with your agent.`, {
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: 1,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indiccoder/mentis-cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
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
  }
@@ -99,29 +99,28 @@ export class ReplManager {
99
99
  }
100
100
 
101
101
  public async start() {
102
- UIManager.displayLogo();
103
- UIManager.displayWelcome();
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(); // readline expects newest first? No, newest is usually 0? Check.
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
- UIManager.printSeparator();
119
- // console.log(chalk.dim(` /help for help | Model: ${chalk.cyan(this.currentModelName)}`));
120
- // Removed redundancy to keep CLI clean, prompt has info? No, prompt is minimal.
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 modeLabel = this.mode === 'PLAN' ? chalk.magenta('PLAN') : chalk.blue('BUILD');
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
  }
@@ -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', // Use a block-like font
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.0 - AI Coding Agent'));
17
+ console.log(chalk.gray(' v1.0.5 - AI Coding Agent'));
18
18
  console.log('');
19
19
  }
20
20
 
21
- public static displayWelcome() {
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
- `${chalk.bold('Welcome to Mentis-CLI')}\n\n` +
25
- `• Type ${chalk.cyan('/help')} for commands.\n` +
26
- `• Type ${chalk.cyan('/config')} to setup your model.\n` +
27
- `• Start typing to chat with your agent.`,
28
- {
29
- padding: 1,
30
- margin: 1,
31
- borderStyle: 'round',
32
- borderColor: 'cyan',
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
+ }