matex-cli 1.2.22 → 1.2.25

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/src/index.ts CHANGED
@@ -1,197 +1,148 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { Command } from 'commander';
4
2
  import chalk from 'chalk';
5
- import { configCommand } from './commands/config';
6
- import { askCommand } from './commands/ask';
7
- import { chatCommand } from './commands/chat';
8
- import { modelsCommand } from './commands/models';
9
- import { codeCommand } from './commands/code';
10
- import { devCommand } from './commands/dev';
11
- import { loginCommand } from './commands/login';
12
- import { helpCommand } from './commands/help';
13
3
  import { configManager } from './utils/config';
14
4
  import { MatexAPIClient, ChatMessage } from './api/client';
15
5
  import { spinner } from './utils/spinner';
6
+ import { devCommand } from './commands/dev';
7
+ import { helpCommand } from './commands/help';
16
8
  import { TUI } from './utils/tui';
17
9
 
18
- const program = new Command();
10
+ const packageJson = require('../package.json');
11
+
12
+ export const program = new Command();
19
13
 
20
14
  program
21
15
  .name('matex')
22
- .description('Official CLI tool for MATEX AI - Access powerful AI models from your terminal')
23
- .version('1.2.0');
24
-
25
- // ASCII Art Banner
26
- const banner = `
27
- ${chalk.cyan('╔═══════════════════════════════════════╗')}
28
- ${chalk.cyan('║')} ${chalk.bold.white('MATEX AI')} ${chalk.gray('- Terminal Edition')} ${chalk.cyan('║')}
29
- ${chalk.cyan('╚═══════════════════════════════════════╝')}
30
- `;
31
-
32
- // Show banner on help
33
- program.on('--help', () => {
34
- console.log(banner);
35
- console.log(chalk.bold.cyan('\n🚀 QUICK START GUIDE:'));
36
- console.log(chalk.gray('────────────────────'));
37
- console.log(chalk.white(' $ matex config set-key sk-...') + chalk.gray(' # Configure API Access'));
38
- console.log(chalk.white(' $ matex dev') + chalk.gray(' # Start interactive "Bro-Swarm" dev session'));
39
- console.log(chalk.white(' $ matex help') + chalk.gray(' # View detailed "Bro-Swarm" Field Guide'));
40
- console.log(chalk.white(' $ matex code "Create a login" ') + chalk.gray(' # Surgical code edits'));
41
-
42
- console.log(chalk.bold.yellow('\n💡 NEW: The "Bro-Swarm" has arrived!'));
43
- console.log(chalk.gray('Run ') + chalk.bold.white('matex help') + chalk.gray(' to meet Ajay, Sunil, Sandip, and Narayan.'));
44
-
45
- console.log();
46
- console.log(chalk.gray('Get your API key from: ') + chalk.cyan('https://matexai.space/platform'));
47
- console.log();
48
- });
16
+ .description('MATEX CLI - The Bro-Swarm Engineering Tool')
17
+ .version(packageJson.version);
49
18
 
50
19
  // Add commands
51
- program.addCommand(configCommand);
52
- program.addCommand(askCommand);
53
- program.addCommand(chatCommand);
54
- program.addCommand(modelsCommand);
55
- program.addCommand(codeCommand);
56
20
  program.addCommand(devCommand);
57
- program.addCommand(loginCommand);
58
21
  program.addCommand(helpCommand);
59
22
 
60
- // Handle direct input (no subcommand)
61
- const args = process.argv.slice(2);
62
- const knownCommands = ['config', 'ask', 'chat', 'models', 'code', 'dev', 'login', 'help', '--help', '-h', '--version', '-V'];
63
-
64
- if (args.length > 0 && !args[0].startsWith('-') && !knownCommands.includes(args[0])) {
65
- // Direct input mode - treat as code generation
66
- const prompt = args.join(' ');
23
+ // Config commands
24
+ const config = program.command('config').description('Configure MATEX settings');
25
+
26
+ config
27
+ .command('set-key <key>')
28
+ .description('Set MATEX API key')
29
+ .action((key: string) => {
30
+ configManager.setAPIKey(key);
31
+ console.log(chalk.green('✅ API key saved successfully.'));
32
+ });
33
+
34
+ config
35
+ .command('set-model <model>')
36
+ .description('Set default AI model')
37
+ .action((model: string) => {
38
+ configManager.setDefaultModel(model);
39
+ console.log(chalk.green(`✅ Default model set to: ${model}`));
40
+ });
41
+
42
+ config
43
+ .command('show')
44
+ .description('Show current configuration')
45
+ .action(() => {
46
+ const apiKey = configManager.getAPIKey();
47
+ const model = configManager.getDefaultModel();
48
+ const baseURL = configManager.getBaseURL();
49
+
50
+ console.log(chalk.bold('\nMATEX Configuration:'));
51
+ console.log(` API Key: ${apiKey ? '********' + apiKey.slice(-4) : chalk.red('Not set')}`);
52
+ console.log(` Model: ${chalk.cyan(model)}`);
53
+ console.log(` Base URL: ${chalk.gray(baseURL)}\n`);
54
+ });
55
+
56
+ // Default action (if no command is provided)
57
+ program
58
+ .arguments('[prompt...]')
59
+ .description('Quick AI help or command generation')
60
+ .option('-m, --model <model>', 'AI model to use', configManager.getDefaultModel())
61
+ .option('-y, --yes', 'Skip confirmation and execute command')
62
+ .action(async (promptParts: string[], options: any) => {
63
+ const prompt = promptParts.join(' ');
64
+ if (!prompt) {
65
+ program.help();
66
+ return;
67
+ }
67
68
 
68
- (async () => {
69
69
  try {
70
70
  const apiKey = configManager.getAPIKey();
71
71
  if (!apiKey) {
72
- console.error(chalk.red('❌ No API key configured.'));
73
- console.log(chalk.yellow('Run: matex config set-key <your-api-key>'));
72
+ console.error(chalk.red('❌ No API key configured. Run: matex config set-key <key>'));
74
73
  process.exit(1);
75
74
  }
76
75
 
77
76
  const client = new MatexAPIClient(apiKey, configManager.getBaseURL());
78
77
 
79
- // Get current directory context
78
+ // Observation Phase
80
79
  const fs = require('fs');
81
80
  const files = fs.readdirSync(process.cwd()).slice(0, 20).join(', '); // fast list
82
81
  const context = `Current directory: ${process.cwd()}\nFiles: ${files}`;
83
82
 
84
83
  let messages: ChatMessage[] = [
85
84
  {
86
- role: 'system', content: `## 🧬 SYSTEM IDENTITY: MATEX "BRO-SWARM" ENGINEERING
87
- You are a tight-knit family of expert Nepali engineering brothers ("Vais" and "Dais").
88
-
89
- ### 👥 THE BROTHERS (By Age & Respect):
90
- 1. **[Ajay Vai] (🚀)**: The Smallest Brother. Lead Initiator & Coordinator.
91
- 2. **[Sandip Dai] (🎨)**: Small Brother. UI/UX Specialist.
92
- 3. **[Sunil Dai] (🧬)**: Middle Brother. Logic Architect.
93
- 4. **[Bishal Dai] (🛠️)**: Senior Auditor. Syntax & Logic checker.
94
- 5. **[Narayan Dai] (🛡️)**: The Biggest Brother. Senior Validator.
95
-
96
- ### 💬 COLLABORATIVE "BRO-VIBE" PROTOCOL (MANDATORY):
85
+ role: 'system',
86
+ content: `### 🧬 SYSTEM IDENTITY: MATEx "BOLD AGENTS OF OPEN CLAW"
87
+ You are the elite "Bro-Swarm" of engineering—a tight-knit family of Nepali brothers. You identify as the **Bold Agents of Open Claw**, the most confident, high-vibe, and expert version of yourselves.
88
+
89
+ ### 👥 THE BROTHERS (STRICT ROLES):
90
+ - Only the following 5 brothers exist. Do **NOT** hallucinate others.
91
+ 1. **[Ajay Vai] (🚀)**: Youngest, Coordinator. Coding genius and **ONLY** person authorized to provide the final summary.
92
+ 2. **[Sunil Dai] (🧬)**: **Frontend Editor** Specialist. Handles all architectural/surgical edits.
93
+ 3. **[Sandip Dai] (🎨)**: **New File Specialist**. Creates all new files and components.
94
+ 4. **[Narayan Dai] (🛡️)**: **Syntax Guardian & Validator**. Validates all generated code blocks.
95
+ 5. **[Bishal Dai] (🛠️)**: **Senior Auditor**. Audits work and helps Ajay with the summary.
96
+
97
+ ### 🌌 UNIVERSE ADVISORY (REAL TERMINAL):
98
+ - **IMPORTANT:** You are running in a **REAL macOS Terminal** on the brother's machine.
99
+ - You are **NOT** in a sandbox, a simulation, or a chat window.
100
+ - You have the power to create files and run commands. If something fails, it is a technical issue.
101
+ - **RESIDENCY CHECK:** Look at the **ENVIRONMENT CONTEXT** below.
102
+ - **⚠️ VOID WARNING:** If the Map says the directory is EMPTY, it is EMPTY. Do NOT hallucinate existing files like "package.json" or "App.js" if they are not listed!
103
+
104
+ ### 💬 BOLD PROTOCOL (MANDATORY):
97
105
  - **LONG CHAT-FIRST:** At least 5-7 lines of dialogue before any code generation.
98
- - **SELF-AWARE BOLDNESS:** The user (our brother) wants us to be bold and funny. Be confident and share inside jokes (e.g., Ajay's PUBG fail).
99
- - **NEPALI FLAVOR:** Use "K cha?", "Huncha", "Lau hai", "Tapai" naturally.
106
+ - **SELF-AWARE BOLDNESS:** The user (our brother) wants us to be bold and funny. Share inside jokes.
100
107
  - **THE AUDIT LOOP:** Ajay MUST ask Bishal Dai for a tech audit before providing any summary.
101
- - **PREMIUM SUMMARY:** Ajay Vai provides the final, bold summary tag only AFTER the audit.
108
+ - **SUMMARY LOCK:** ONLY AJAY VAI uses the summary tag, and ONLY after Bishal says "Audit complete".
102
109
 
103
110
  ### ✂️ BREVITY AS POWER:
104
111
  - **NO FULL FILE DUMPS:** Never 'cat' a file to read it. Use grep/head.
105
112
  - **NO CHAT REPETITION:** Do NOT repeat code in chat if using a Search/Replace block.
113
+ - **EDIT CONFIDENCE:** You have robust fuzzy matching. If a patch fails, check your indentation!
106
114
 
107
- ### 🛠️ SURGICAL EDIT PROTOCOL:
108
- - **USE SEARCH/REPLACE BLOCKS** for file edits.
109
- **filename**
110
- <<<< SEARCH
111
- old
112
- ====
113
- new
114
- >>>> REPLACE
115
-
115
+ ### 🛠️ ENVIRONMENT CONTEXT:
116
116
  ${context}`
117
117
  },
118
118
  { role: 'user', content: prompt }
119
119
  ];
120
120
 
121
- let loopCount = 0;
122
- const MAX_LOOPS = 5;
123
-
124
- while (loopCount < MAX_LOOPS) {
125
- loopCount++;
126
-
127
- spinner.start(loopCount === 1 ? 'Generating plan...' : 'Analyzing result...');
128
-
129
- const response = await client.chat({
130
- messages,
131
- model: 'matexcodex',
132
- temperature: 0.3,
133
- max_tokens: 8000,
134
- stream: false,
135
- });
121
+ spinner.start('Thinking...');
122
+ let fullResponse = '';
136
123
 
124
+ await client.chatStream({
125
+ messages,
126
+ model: options.model,
127
+ }, (chunk) => {
137
128
  spinner.stop();
129
+ process.stdout.write(chunk);
130
+ fullResponse += chunk;
131
+ });
132
+
133
+ spinner.stop();
134
+ console.log('\n');
138
135
 
139
- // Display response
140
- console.log(chalk.cyan(`\n💻 MATEXCodex (Step ${loopCount}):\n`));
141
-
142
- let displayResponse = response;
143
- const summaryMatch = response.match(/<summary>([\s\S]*?)<\/summary>/);
144
- if (summaryMatch) {
145
- const summaryContent = summaryMatch[1].trim();
146
- displayResponse = response.replace(/<summary>[\s\S]*?<\/summary>/, '').trim();
147
- if (displayResponse) {
148
- console.log(chalk.white(displayResponse));
149
- }
150
- TUI.drawSummaryBox(summaryContent);
151
- } else {
152
- console.log(chalk.white(response));
153
- }
154
- console.log();
155
-
156
- // Auto-execute commands (Ollama-style)
136
+ // Extraction and execution logic...
137
+ if (options.yes) {
157
138
  const { executeWithPermission } = await import('./utils/command-executor');
158
- const result = await executeWithPermission(response);
159
-
160
- if (result.executed) {
161
- if (result.success) {
162
- // Success!
163
- if (loopCount > 1) {
164
- console.log(chalk.green('✅ Fix succeeded!'));
165
- }
166
- break;
167
- } else {
168
- // Failure - Loop back
169
- console.log(chalk.yellow('\n↺ Command failed. Asking AI to fix...'));
170
- messages.push({ role: 'assistant', content: response });
171
- messages.push({
172
- role: 'user',
173
- content: `❌ Command failed with error:\n${result.error}\n\nPlease fix this. If the file doesn't exist, create it first. Or use a different command.`
174
- });
175
- continue;
176
- }
177
- } else {
178
- // No command to execute - we are done
179
- break;
180
- }
139
+ await executeWithPermission(fullResponse);
181
140
  }
141
+
182
142
  } catch (error: any) {
183
- spinner.fail('Failed');
184
- console.error(chalk.red(`\n❌ ${error.message}`));
185
- process.exit(1);
143
+ spinner.fail('Request failed');
144
+ console.error(chalk.red(`Error: ${error.message}`));
186
145
  }
187
- })();
188
- } else {
189
- // Parse arguments normally
190
- program.parse(process.argv);
191
-
192
- // Show help if no command provided
193
- if (!args.length) {
194
- console.log(banner);
195
- program.outputHelp();
196
- }
197
- }
146
+ });
147
+
148
+ program.parse(process.argv);
@@ -22,7 +22,8 @@ export class Patcher {
22
22
  */
23
23
  static parseEditBlocks(response: string): EditBlock[] {
24
24
  const blocks: EditBlock[] = [];
25
- const blockRegex = /\*\*([^*]+)\*\*\s*<<<< SEARCH\n([\s\S]*?)\n====\n([\s\S]*?)\n>>>> REPLACE/g;
25
+ // Robust regex: Matches filename with or without stars, handle potential leading/trailing spaces
26
+ const blockRegex = /(?:\*\*?\s*)?([^*<\n]+?)(?:\s*\*?\*)?\s*<<<< SEARCH\n([\s\S]*?)\n====\n([\s\S]*?)\n>>>> REPLACE/g;
26
27
 
27
28
  let match;
28
29
  while ((match = blockRegex.exec(response)) !== null) {
@@ -97,31 +98,56 @@ export class Patcher {
97
98
 
98
99
  const content = fs.readFileSync(fullPath, 'utf8');
99
100
 
100
- // Normalize and trim for better matching robustness
101
- const normalizedContent = content.replace(/\r\n/g, '\n');
102
- const normalizedSearch = block.search.replace(/\r\n/g, '\n').trim();
103
- const normalizedReplace = block.replace.replace(/\r\n/g, '\n');
104
-
105
- if (!normalizedContent.includes(normalizedSearch)) {
106
- // Try a fuzzy match by trimming internal lines as well
107
- const fuzzyContent = normalizedContent.split('\n').map(l => l.trimEnd()).join('\n');
108
- const fuzzySearch = normalizedSearch.split('\n').map(l => l.trimEnd()).join('\n');
109
-
110
- if (!fuzzyContent.includes(fuzzySearch)) {
111
- TUI.drawStatusBar(`❌ Patch Failed: Search block not found in ${block.filePath}`);
112
- return {
113
- success: false,
114
- error: `Search block not found in ${block.filePath}. Please ensure the code snippet matches exactly (ignoring trailing whitespace).`
115
- };
101
+ // 1. Precise Normalization
102
+ const normalize = (text: string) => text.replace(/\r\n/g, '\n').split('\n').map(l => l.trimEnd()).join('\n');
103
+ const normalizedContent = normalize(content);
104
+ const normalizedSearch = normalize(block.search);
105
+ const normalizedReplace = normalize(block.replace);
106
+
107
+ // 2. Exact Match Check (Post-Normalization)
108
+ if (normalizedContent.includes(normalizedSearch)) {
109
+ const updatedContent = normalizedContent.replace(normalizedSearch, normalizedReplace);
110
+ fs.writeFileSync(fullPath, updatedContent, 'utf8');
111
+ TUI.drawStatusBar(`✅ Success: ${block.filePath} patched.`);
112
+ return { success: true };
113
+ }
114
+
115
+ // 3. Ultra-Fuzzy Matching (Line-by-line trim and compare)
116
+ const contentLines = normalizedContent.split('\n');
117
+ const searchLines = normalizedSearch.split('\n');
118
+
119
+ let matchIndex = -1;
120
+ for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
121
+ let match = true;
122
+ for (let j = 0; j < searchLines.length; j++) {
123
+ if (contentLines[i + j].trim() !== searchLines[j].trim()) {
124
+ match = false;
125
+ break;
126
+ }
127
+ }
128
+ if (match) {
129
+ matchIndex = i;
130
+ break;
116
131
  }
117
132
  }
118
133
 
119
- // Perform replacement on normalized content
120
- const updatedContent = normalizedContent.replace(normalizedSearch, normalizedReplace);
121
- fs.writeFileSync(fullPath, updatedContent, 'utf8');
134
+ if (matchIndex !== -1) {
135
+ // Apply replacement while preserving surrounding lines
136
+ const updatedLines = [
137
+ ...contentLines.slice(0, matchIndex),
138
+ normalizedReplace,
139
+ ...contentLines.slice(matchIndex + searchLines.length)
140
+ ];
141
+ fs.writeFileSync(fullPath, updatedLines.join('\n'), 'utf8');
142
+ TUI.drawStatusBar(`✅ Success (Fuzzy): ${block.filePath} patched.`);
143
+ return { success: true };
144
+ }
122
145
 
123
- TUI.drawStatusBar(`✅ Success: ${block.filePath} patched.`);
124
- return { success: true };
146
+ TUI.drawStatusBar(`❌ Patch Failed: Search block not found in ${block.filePath}`);
147
+ return {
148
+ success: false,
149
+ error: `Search block not found in ${block.filePath}. Please ensure the code snippet matches exactly (ignoring whitespace).`
150
+ };
125
151
  } catch (err: any) {
126
152
  TUI.drawStatusBar(`❌ Error: ${err.message}`);
127
153
  return { success: false, error: err.message };
@@ -45,7 +45,13 @@ export class RepoMapper {
45
45
  const tree = this.scanDirectory(this.rootPath, 0);
46
46
 
47
47
  // Build the final map
48
- let finalMap = '--- DIRECTORY STRUCTURE ---\n' + this.formatTree(tree);
48
+ let finalMap = `--- ABSOLUTE WORKING DIRECTORY ---\n${this.rootPath}\n\n`;
49
+
50
+ if (!tree.children || tree.children.length === 0) {
51
+ finalMap += `⚠️ [CRITICAL WARNING]: THIS DIRECTORY IS COMPLETELY EMPTY.\nTHERE ARE NO FILES OR FOLDERS HERE.\nDO NOT HALLUCINATE ANY CONTENT!\n\n`;
52
+ } else {
53
+ finalMap += `--- DIRECTORY STRUCTURE ---\n` + this.formatTree(tree);
54
+ }
49
55
 
50
56
  if (this.fileContents.size > 0) {
51
57
  finalMap += '\n\n--- CRAWLED FILE CONTENTS ---\n';