@indiccoder/mentis-cli 1.0.3

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.
Files changed (46) hide show
  1. package/README.md +90 -0
  2. package/console.log(tick) +0 -0
  3. package/debug_fs.js +12 -0
  4. package/dist/checkpoint/CheckpointManager.js +53 -0
  5. package/dist/config/ConfigManager.js +55 -0
  6. package/dist/context/ContextManager.js +55 -0
  7. package/dist/context/RepoMapper.js +112 -0
  8. package/dist/index.js +12 -0
  9. package/dist/llm/AnthropicClient.js +70 -0
  10. package/dist/llm/ModelInterface.js +2 -0
  11. package/dist/llm/OpenAIClient.js +58 -0
  12. package/dist/mcp/JsonRpcClient.js +117 -0
  13. package/dist/mcp/McpClient.js +59 -0
  14. package/dist/repl/PersistentShell.js +75 -0
  15. package/dist/repl/ReplManager.js +813 -0
  16. package/dist/tools/FileTools.js +100 -0
  17. package/dist/tools/GitTools.js +127 -0
  18. package/dist/tools/PersistentShellTool.js +30 -0
  19. package/dist/tools/SearchTools.js +83 -0
  20. package/dist/tools/Tool.js +2 -0
  21. package/dist/tools/WebSearchTool.js +60 -0
  22. package/dist/ui/UIManager.js +40 -0
  23. package/package.json +63 -0
  24. package/screenshot_1765779883482_9b30.png +0 -0
  25. package/scripts/test_features.ts +48 -0
  26. package/scripts/test_glm.ts +53 -0
  27. package/scripts/test_models.ts +38 -0
  28. package/src/checkpoint/CheckpointManager.ts +61 -0
  29. package/src/config/ConfigManager.ts +77 -0
  30. package/src/context/ContextManager.ts +63 -0
  31. package/src/context/RepoMapper.ts +119 -0
  32. package/src/index.ts +12 -0
  33. package/src/llm/ModelInterface.ts +47 -0
  34. package/src/llm/OpenAIClient.ts +64 -0
  35. package/src/mcp/JsonRpcClient.ts +103 -0
  36. package/src/mcp/McpClient.ts +75 -0
  37. package/src/repl/PersistentShell.ts +85 -0
  38. package/src/repl/ReplManager.ts +842 -0
  39. package/src/tools/FileTools.ts +89 -0
  40. package/src/tools/GitTools.ts +113 -0
  41. package/src/tools/PersistentShellTool.ts +32 -0
  42. package/src/tools/SearchTools.ts +74 -0
  43. package/src/tools/Tool.ts +6 -0
  44. package/src/tools/WebSearchTool.ts +63 -0
  45. package/src/ui/UIManager.ts +41 -0
  46. package/tsconfig.json +21 -0
@@ -0,0 +1,89 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { Tool } from './Tool';
4
+
5
+ export class WriteFileTool implements Tool {
6
+ name = 'write_file';
7
+ description = 'Write content to a file. Overwrites if exists. Creates directories if needed.';
8
+ parameters = {
9
+ type: 'object',
10
+ properties: {
11
+ filePath: {
12
+ type: 'string',
13
+ description: 'The path to the file to write',
14
+ },
15
+ content: {
16
+ type: 'string',
17
+ description: 'The content to write to the file',
18
+ },
19
+ },
20
+ required: ['filePath', 'content'],
21
+ };
22
+
23
+ async execute(args: { filePath: string; content: string }): Promise<string> {
24
+ try {
25
+ const absolutePath = path.resolve(args.filePath);
26
+ await fs.ensureDir(path.dirname(absolutePath));
27
+ await fs.writeFile(absolutePath, args.content, 'utf-8');
28
+ return `Successfully wrote to ${args.filePath}`;
29
+ } catch (error: any) {
30
+ return `Error writing file: ${error.message}`;
31
+ }
32
+ }
33
+ }
34
+
35
+ export class ReadFileTool implements Tool {
36
+ name = 'read_file';
37
+ description = 'Read content from a file.';
38
+ parameters = {
39
+ type: 'object',
40
+ properties: {
41
+ filePath: {
42
+ type: 'string',
43
+ description: 'The path to the file to read',
44
+ },
45
+ },
46
+ required: ['filePath'],
47
+ };
48
+
49
+ async execute(args: { filePath: string }): Promise<string> {
50
+ try {
51
+ const absolutePath = path.resolve(args.filePath);
52
+ if (!await fs.pathExists(absolutePath)) {
53
+ return `Error: File not found at ${args.filePath}`;
54
+ }
55
+ const content = await fs.readFile(absolutePath, 'utf-8');
56
+ return content;
57
+ } catch (error: any) {
58
+ return `Error reading file: ${error.message}`;
59
+ }
60
+ }
61
+ }
62
+
63
+ export class ListDirTool implements Tool {
64
+ name = 'list_dir';
65
+ description = 'List files and directories in a path.';
66
+ parameters = {
67
+ type: 'object',
68
+ properties: {
69
+ path: {
70
+ type: 'string',
71
+ description: 'The directory path to list',
72
+ },
73
+ },
74
+ required: ['path'],
75
+ };
76
+
77
+ async execute(args: { path: string }): Promise<string> {
78
+ try {
79
+ const targetPath = path.resolve(args.path);
80
+ if (!await fs.pathExists(targetPath)) {
81
+ return `Error: Directory not found at ${args.path}`;
82
+ }
83
+ const files = await fs.readdir(targetPath);
84
+ return files.join('\n');
85
+ } catch (error: any) {
86
+ return `Error listing directory: ${error.message}`;
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,113 @@
1
+ import { Tool } from './Tool';
2
+ import { exec } from 'child_process';
3
+ import util from 'util';
4
+
5
+ const execAsync = util.promisify(exec);
6
+
7
+ export class GitStatusTool implements Tool {
8
+ name = 'git_status';
9
+ description = 'Check the status of the git repository.';
10
+ parameters = {
11
+ type: 'object',
12
+ properties: {},
13
+ required: []
14
+ };
15
+
16
+ async execute(args: any): Promise<string> {
17
+ try {
18
+ const { stdout } = await execAsync('git status');
19
+ return stdout;
20
+ } catch (error: any) {
21
+ return `Error running git status: ${error.message}`;
22
+ }
23
+ }
24
+ }
25
+
26
+ export class GitDiffTool implements Tool {
27
+ name = 'git_diff';
28
+ description = 'Show changes in the git repository.';
29
+ parameters = {
30
+ type: 'object',
31
+ properties: {
32
+ cached: {
33
+ type: 'boolean',
34
+ description: 'Show cached (staged) changes.'
35
+ }
36
+ },
37
+ required: []
38
+ };
39
+
40
+ async execute(args: { cached?: boolean }): Promise<string> {
41
+ try {
42
+ const cmd = args.cached ? 'git diff --cached' : 'git diff';
43
+ const { stdout } = await execAsync(cmd);
44
+ return stdout || 'No changes found.';
45
+ } catch (error: any) {
46
+ return `Error running git diff: ${error.message}`;
47
+ }
48
+ }
49
+ }
50
+
51
+ export class GitCommitTool implements Tool {
52
+ name = 'git_commit';
53
+ description = 'Commit changes to the git repository.';
54
+ parameters = {
55
+ type: 'object',
56
+ properties: {
57
+ message: {
58
+ type: 'string',
59
+ description: 'The commit message.'
60
+ }
61
+ },
62
+ required: ['message']
63
+ };
64
+
65
+ async execute(args: { message: string }): Promise<string> {
66
+ try {
67
+ // Escape double quotes in message
68
+ const safeMessage = args.message.replace(/"/g, '\\"');
69
+ const { stdout } = await execAsync(`git commit -m "${safeMessage}"`);
70
+ return stdout;
71
+ } catch (error: any) {
72
+ return `Error running git commit: ${error.message}`;
73
+ }
74
+ }
75
+ }
76
+
77
+ export class GitPushTool implements Tool {
78
+ name = 'git_push';
79
+ description = 'Push changes to the remote repository.';
80
+ parameters = {
81
+ type: 'object',
82
+ properties: {},
83
+ required: []
84
+ };
85
+
86
+ async execute(args: any): Promise<string> {
87
+ try {
88
+ const { stdout, stderr } = await execAsync('git push');
89
+ return stdout + (stderr ? `\nStderr: ${stderr}` : '');
90
+ } catch (error: any) {
91
+ return `Error running git push: ${error.message}`;
92
+ }
93
+ }
94
+ }
95
+
96
+ export class GitPullTool implements Tool {
97
+ name = 'git_pull';
98
+ description = 'Pull changes from the remote repository.';
99
+ parameters = {
100
+ type: 'object',
101
+ properties: {},
102
+ required: []
103
+ };
104
+
105
+ async execute(args: any): Promise<string> {
106
+ try {
107
+ const { stdout } = await execAsync('git pull');
108
+ return stdout;
109
+ } catch (error: any) {
110
+ return `Error running git pull: ${error.message}`;
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,32 @@
1
+ import { Tool } from './Tool';
2
+ import { PersistentShell } from '../repl/PersistentShell';
3
+
4
+ export class PersistentShellTool implements Tool {
5
+ name = 'run_shell';
6
+ description = 'Execute a shell command in a persistent session. Use this for running tests, build scripts, or git commands. State (env vars, cwd) is preserved.';
7
+ parameters = {
8
+ type: 'object',
9
+ properties: {
10
+ command: {
11
+ type: 'string',
12
+ description: 'The shell command to execute.'
13
+ }
14
+ },
15
+ required: ['command']
16
+ };
17
+
18
+ private shell: PersistentShell;
19
+
20
+ constructor(shell: PersistentShell) {
21
+ this.shell = shell;
22
+ }
23
+
24
+ async execute(args: { command: string }): Promise<string> {
25
+ try {
26
+ const output = await this.shell.execute(args.command);
27
+ return output || 'Command executed with no output.';
28
+ } catch (error: any) {
29
+ return `Error executing command: ${error.message}`;
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,74 @@
1
+ import { Tool } from './Tool';
2
+ import { exec } from 'child_process';
3
+ import util from 'util';
4
+ import path from 'path';
5
+
6
+ const execAsync = util.promisify(exec);
7
+
8
+ export class SearchFileTool implements Tool {
9
+ name = 'search_files';
10
+ description = 'Search for a string pattern in files within the current directory recursively.';
11
+ parameters = {
12
+ type: 'object',
13
+ properties: {
14
+ query: {
15
+ type: 'string',
16
+ description: 'The string or regular expression to search for.'
17
+ },
18
+ path: {
19
+ type: 'string',
20
+ description: 'Optional path to limit search (default: .)'
21
+ }
22
+ },
23
+ required: ['query']
24
+ };
25
+
26
+ async execute(args: { query: string, path?: string }): Promise<string> {
27
+ try {
28
+ const searchPath = args.path || '.';
29
+ // Use git grep if available as it respects .gitignore, otherwise fallback to grep or findstr
30
+ // For simplicity/cross-platform in this environment, let's try a heuristic:
31
+ // win32 usually has findstr, but git grep is better if installed.
32
+ // We'll assume standard 'grep -r' works in many envs or user has git bash.
33
+ // Actually, safer to use 'git grep -n "query"' if inside a repo.
34
+
35
+ const command = `git grep -n "${args.query}" ${searchPath}`;
36
+ const { stdout } = await execAsync(command);
37
+ return stdout || 'No matches found.';
38
+ } catch (error: any) {
39
+ // grep returns exit code 1 if not found, distinct from error
40
+ if (error.code === 1) return 'No matches found.';
41
+ return `Error searching: ${error.message}`;
42
+ }
43
+ }
44
+ }
45
+
46
+ export class RunShellTool implements Tool {
47
+ name = 'run_shell';
48
+ description = 'Execute a shell command. Use this for running tests, build scripts, or git commands.';
49
+ parameters = {
50
+ type: 'object',
51
+ properties: {
52
+ command: {
53
+ type: 'string',
54
+ description: 'The shell command to execute.'
55
+ }
56
+ },
57
+ required: ['command']
58
+ };
59
+
60
+ async execute(args: { command: string }): Promise<string> {
61
+ // Safety: We might want to block dangerous commands, but user approval is better.
62
+ // For now, relies on the ReplManager's safety/approval loop if we add it for this tool too.
63
+ // Or we just allow it since it's a CLI tool for devs.
64
+ try {
65
+ const { stdout, stderr } = await execAsync(args.command);
66
+ let result = '';
67
+ if (stdout) result += `STDOUT:\n${stdout}\n`;
68
+ if (stderr) result += `STDERR:\n${stderr}\n`;
69
+ return result || 'Command executed with no output.';
70
+ } catch (error: any) {
71
+ return `Error executing command: ${error.message}\nSTDOUT: ${error.stdout}\nSTDERR: ${error.stderr}`;
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,6 @@
1
+ export interface Tool {
2
+ name: string;
3
+ description: string;
4
+ parameters: object;
5
+ execute(args: any): Promise<string>;
6
+ }
@@ -0,0 +1,63 @@
1
+ import { Tool } from './Tool';
2
+ import { search } from 'duck-duck-scrape';
3
+ import chalk from 'chalk';
4
+
5
+ export class WebSearchTool implements Tool {
6
+ name = 'search_web';
7
+ description = 'Search the internet for documentation, libraries, or solutions to errors. Returns snippets of top results.';
8
+ parameters = {
9
+ type: 'object',
10
+ properties: {
11
+ query: {
12
+ type: 'string',
13
+ description: 'The search query.'
14
+ }
15
+ },
16
+ required: ['query']
17
+ };
18
+
19
+ async execute(args: { query: string }): Promise<string> {
20
+ try {
21
+ // Priority 1: Google Search
22
+ try {
23
+ // Dynamic import to avoid build issues if types are missing
24
+ const { search: googleSearch } = require('google-sr');
25
+ console.log(chalk.dim(` Searching Google for: "${args.query}"...`));
26
+
27
+ const googleResults: any[] = await googleSearch({
28
+ query: args.query,
29
+ limit: 5,
30
+ });
31
+
32
+ if (googleResults && googleResults.length > 0) {
33
+ const formatted = googleResults.map(r =>
34
+ `[${r.title}](${r.link})\n${r.description || 'No description.'}`
35
+ ).join('\n\n');
36
+ return `Top Google Results:\n\n${formatted}`;
37
+ }
38
+ } catch (googleError: any) {
39
+ console.log(chalk.dim(` Google search failed (${googleError.message}), failing over to DuckDuckGo...`));
40
+ }
41
+
42
+ // Priority 2: DuckDuckGo Fallback
43
+ console.log(chalk.dim(` Searching DuckDuckGo for: "${args.query}"...`));
44
+ const ddgResults = await search(args.query, {
45
+ safeSearch: 0
46
+ });
47
+
48
+ if (!ddgResults.results || ddgResults.results.length === 0) {
49
+ return 'No results found.';
50
+ }
51
+
52
+ // Return top 5 results
53
+ const topResults = ddgResults.results.slice(0, 5).map(r =>
54
+ `[${r.title}](${r.url})\n${r.description || 'No description found.'}`
55
+ ).join('\n\n');
56
+
57
+ return `Top Search Results (via DDG):\n\n${topResults}`;
58
+
59
+ } catch (error: any) {
60
+ return `Error searching web: ${error.message}`;
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,41 @@
1
+ import figlet from 'figlet';
2
+ import gradient from 'gradient-string';
3
+ import boxen from 'boxen';
4
+ import chalk from 'chalk';
5
+
6
+ export class UIManager {
7
+ public static displayLogo() {
8
+ console.clear();
9
+ const logoText = figlet.textSync('MENTIS', {
10
+ font: 'ANSI Shadow', // Use a block-like font
11
+ horizontalLayout: 'default',
12
+ verticalLayout: 'default',
13
+ width: 100,
14
+ whitespaceBreak: true,
15
+ });
16
+ console.log(gradient.pastel.multiline(logoText));
17
+ console.log(chalk.gray(' v1.0.0 - AI Coding Agent'));
18
+ console.log('');
19
+ }
20
+
21
+ public static displayWelcome() {
22
+ 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
+ )
35
+ );
36
+ }
37
+
38
+ public static printSeparator() {
39
+ console.log(chalk.gray('──────────────────────────────────────────────────'));
40
+ }
41
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": [
6
+ "ES2020"
7
+ ],
8
+ "outDir": "./dist",
9
+ "rootDir": "./src",
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true
14
+ },
15
+ "include": [
16
+ "src/**/*"
17
+ ],
18
+ "exclude": [
19
+ "node_modules"
20
+ ]
21
+ }