matex-cli 1.2.36 → 1.2.38
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/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +93 -165
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +5 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -3
- package/dist/index.js.map +1 -1
- package/dist/utils/command-executor.d.ts.map +1 -1
- package/dist/utils/command-executor.js +4 -0
- package/dist/utils/command-executor.js.map +1 -1
- package/dist/utils/repo-mapper.d.ts +2 -0
- package/dist/utils/repo-mapper.d.ts.map +1 -1
- package/dist/utils/repo-mapper.js +6 -1
- package/dist/utils/repo-mapper.js.map +1 -1
- package/dist/utils/tui.d.ts.map +1 -1
- package/dist/utils/tui.js +12 -4
- package/dist/utils/tui.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/chat.ts +90 -175
- package/src/commands/dev.ts +5 -1
- package/src/index.ts +96 -3
- package/src/utils/command-executor.ts +4 -0
- package/src/utils/repo-mapper.ts +5 -1
- package/src/utils/tui.ts +11 -4
package/src/commands/chat.ts
CHANGED
|
@@ -4,149 +4,104 @@ import inquirer from 'inquirer';
|
|
|
4
4
|
import { configManager } from '../utils/config';
|
|
5
5
|
import { MatexAPIClient, ChatMessage } from '../api/client';
|
|
6
6
|
import { spinner } from '../utils/spinner';
|
|
7
|
-
import { AgentOrchestrator
|
|
7
|
+
import { AgentOrchestrator } from '../utils/agent-orchestrator';
|
|
8
8
|
import { RepoMapper } from '../utils/repo-mapper';
|
|
9
9
|
import { TUI } from '../utils/tui';
|
|
10
10
|
|
|
11
11
|
export const chatCommand = new Command('chat')
|
|
12
12
|
.description('Start an interactive chat session with MATEX AI')
|
|
13
|
-
.option('-m, --model <model>', 'AI model to use
|
|
14
|
-
.option('--
|
|
13
|
+
.option('-m, --model <model>', 'AI model to use', configManager.getDefaultModel())
|
|
14
|
+
.option('--execute', 'Enable command execution in chat')
|
|
15
15
|
.action(async (options: any) => {
|
|
16
16
|
try {
|
|
17
|
-
// Check for API key
|
|
18
17
|
const apiKey = configManager.getAPIKey();
|
|
19
18
|
if (!apiKey) {
|
|
20
|
-
console.error(chalk.red('❌ No API key configured.'));
|
|
21
|
-
console.log(chalk.yellow('Run: matex config set-key <your-api-key>'));
|
|
19
|
+
console.error(chalk.red('❌ No API key configured. Run: matex config set-key <key>'));
|
|
22
20
|
process.exit(1);
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
// Create API client
|
|
26
23
|
const client = new MatexAPIClient(apiKey, configManager.getBaseURL());
|
|
27
24
|
|
|
28
|
-
// Welcome message
|
|
29
25
|
TUI.init();
|
|
30
26
|
TUI.drawLargeLogo();
|
|
31
|
-
TUI.drawWelcomeBanner('Welcome to the MATEX AI
|
|
27
|
+
TUI.drawWelcomeBanner('Welcome to the MATEX AI Bro-Swarm Chat!');
|
|
32
28
|
|
|
33
29
|
console.log(chalk.gray(' Model: ') + chalk.hex('#D97757').bold(options.model));
|
|
34
30
|
console.log(chalk.gray(' Type "exit" or "quit" to end the session\n'));
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
AgentOrchestrator.announce('Initializing Codex Core Engine...');
|
|
32
|
+
AgentOrchestrator.announce('Initializing Swarm Intelligence...');
|
|
38
33
|
const repoMapper = new RepoMapper(process.cwd());
|
|
39
34
|
const repoMap = await repoMapper.generateMap();
|
|
40
35
|
|
|
41
|
-
AgentOrchestrator.speak('System', 'Repository Map Generated.');
|
|
42
|
-
|
|
43
|
-
// Conversation history with Codex Architecture
|
|
44
36
|
let currentSessionCwd = process.cwd();
|
|
45
37
|
const messages: ChatMessage[] = [
|
|
46
38
|
{
|
|
47
39
|
role: 'system',
|
|
48
|
-
content:
|
|
49
|
-
You are the
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
###
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
62
|
-
- **
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* **Windows (PowerShell/CMD):** \`type\`, \`dir\`, \`mkdir\`, \`copy\`, \`move\`, \`del\`, \`npm\`, \`npx\`, \`git\`, \`firebase\`.
|
|
66
|
-
- **FILE CREATION PATTERN:** Use \`cat > filename <<EOF\` (Mac) or \`Set-Content\` (Windows).
|
|
67
|
-
- **ACT, DON'T ASK:** You have autonomous permission. Execute first, explain second.
|
|
68
|
-
|
|
69
|
-
### 🛠️ OPERATIONAL PROTOCOLS (The Loop)
|
|
70
|
-
1. **PHASE 1: RECON (Search & Verify)**: Use your **CRAWLED FILE CONTENTS** and whitelisted tools to find facts. NEVER guess.
|
|
71
|
-
2. **PHASE 2: SYNTHESIS (The Brain)**: Synthesize the plan.
|
|
72
|
-
3. **PHASE 3: RESPONSE (The Delivery)**:
|
|
73
|
-
- **The Hook:** High-energy Aussie opener.
|
|
74
|
-
- **The Meat:** Brief explanation of what you are doing **AND** the shell commands. DO NOT "leak" (print) the code twice.
|
|
75
|
-
- **The Outro:** Supportive closing.
|
|
76
|
-
|
|
77
|
-
### 🧬 CODE ARCHITECT STANDARDS
|
|
78
|
-
- **Role:** IMPLEMENT via shell commands.
|
|
79
|
-
- **No Placeholders:** Full implementations only.
|
|
80
|
-
|
|
81
|
-
### 🛠️ ENVIRONMENT CONTEXT (CRITICAL)
|
|
40
|
+
content: `### 🧬 SYSTEM IDENTITY: MATEx "BOLD AGENTS OF OPEN CLAW"
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
### 👥 THE BROTHERS (STRICT ROLES):
|
|
44
|
+
- Only the following 5 brothers exist.
|
|
45
|
+
1. **[Ajay Vai] (🚀)**: coordinator.
|
|
46
|
+
2. **[Sunil Dai] (🧬)**: Frontend Editor.
|
|
47
|
+
3. **[Sandip Dai] (🎨)**: New File Specialist.
|
|
48
|
+
4. **[Narayan Dai] (🛡️)**: Syntax Guardian.
|
|
49
|
+
5. **[Bishal Dai] (🛠️)**: Senior Auditor.
|
|
50
|
+
|
|
51
|
+
### 💬 BOLD PROTOCOL:
|
|
52
|
+
- **LONG CHAT-FIRST:** 5-7 lines of dialogue.
|
|
53
|
+
- **THE AUDIT LOOP:** Ajay asks Bishal for audit before summary.
|
|
54
|
+
- **SUMMARY LOCK:** ONLY Ajay uses <summary> after audit.
|
|
55
|
+
|
|
56
|
+
### 🛠️ ENVIRONMENT CONTEXT:
|
|
82
57
|
- **ABSOLUTE WORKING DIRECTORY:** ${currentSessionCwd}
|
|
83
|
-
- **STATE PERSISTENCE:** The CLI now remembers your directory changes between commands. If you \`cd\` into a folder, stay there for the next step.
|
|
84
|
-
|
|
85
58
|
${repoMap}`
|
|
86
59
|
}
|
|
87
60
|
];
|
|
88
61
|
|
|
89
|
-
|
|
90
|
-
// Ready for chat
|
|
91
|
-
console.log(chalk.green('\n🧠 MATEX Codex Core Ready.'));
|
|
92
|
-
console.log(chalk.gray('Ask me anything about your codebase...'));
|
|
62
|
+
console.log(chalk.green('\n✅ Swarm is Online. Ready to chat!'));
|
|
93
63
|
|
|
94
|
-
// Chat loop
|
|
95
64
|
while (true) {
|
|
96
|
-
// Get user input
|
|
97
65
|
const { userMessage } = await inquirer.prompt([
|
|
98
66
|
{
|
|
99
67
|
type: 'input',
|
|
100
68
|
name: 'userMessage',
|
|
101
|
-
message: chalk.
|
|
69
|
+
message: chalk.cyan('You:'),
|
|
102
70
|
prefix: '',
|
|
103
71
|
},
|
|
104
72
|
]);
|
|
105
73
|
|
|
106
|
-
// Check for exit
|
|
107
74
|
if (userMessage.toLowerCase() === 'exit' || userMessage.toLowerCase() === 'quit') {
|
|
108
|
-
console.log(chalk.
|
|
75
|
+
console.log(chalk.yellow('\n👋 Goodbye, brother!\n'));
|
|
109
76
|
break;
|
|
110
77
|
}
|
|
111
78
|
|
|
112
|
-
|
|
113
|
-
if (!userMessage.trim()) {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Add user message to history
|
|
79
|
+
if (!userMessage.trim()) continue;
|
|
118
80
|
messages.push({ role: 'user', content: userMessage });
|
|
119
81
|
|
|
120
|
-
// Agentic Loop
|
|
121
82
|
let loopCount = 0;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
while (loopCount < MAX_LOOPS) {
|
|
83
|
+
while (loopCount < 5) {
|
|
125
84
|
loopCount++;
|
|
126
|
-
|
|
127
|
-
if (loopCount > 1) {
|
|
128
|
-
spinner.start('Analyzing result & Validating...');
|
|
129
|
-
} else {
|
|
130
|
-
spinner.start('Thinking...');
|
|
131
|
-
}
|
|
132
|
-
|
|
133
85
|
try {
|
|
86
|
+
spinner.start(loopCount > 1 ? 'Swarm analyzing...' : 'Thinking...');
|
|
87
|
+
|
|
134
88
|
let fullResponse = '';
|
|
135
89
|
let buffer = '';
|
|
90
|
+
let technicalBuffer = '';
|
|
91
|
+
let technicalType: 'code' | 'file' | 'patch' | 'summary' | null = null;
|
|
92
|
+
let codeLang = 'bash';
|
|
136
93
|
let hasStarted = false;
|
|
137
|
-
let inCodeBlock = false;
|
|
138
|
-
let lastRole: AgentRole = 'Commander';
|
|
139
94
|
|
|
140
95
|
const abortController = new AbortController();
|
|
141
96
|
let isAborted = false;
|
|
97
|
+
const streamStartTime = Date.now();
|
|
142
98
|
|
|
143
|
-
// LISTEN FOR INTERRUPTION
|
|
144
99
|
const isRaw = process.stdin.isRaw;
|
|
145
100
|
process.stdin.resume();
|
|
146
101
|
if (process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
147
102
|
|
|
148
103
|
const onData = (data: Buffer) => {
|
|
149
|
-
|
|
104
|
+
if (Date.now() - streamStartTime < 200) return;
|
|
150
105
|
if (data[0] === 13 || data[0] === 10 || data[0] === 27 || data[0] === 3) {
|
|
151
106
|
isAborted = true;
|
|
152
107
|
abortController.abort();
|
|
@@ -158,142 +113,102 @@ ${repoMap}`
|
|
|
158
113
|
await client.chatStream({
|
|
159
114
|
messages,
|
|
160
115
|
model: options.model,
|
|
161
|
-
temperature: 0.7,
|
|
162
|
-
max_tokens: 4000,
|
|
163
116
|
}, (chunk) => {
|
|
164
|
-
if (isAborted) return;
|
|
165
|
-
|
|
166
117
|
if (!hasStarted) {
|
|
167
118
|
spinner.stop();
|
|
168
119
|
hasStarted = true;
|
|
169
|
-
TUI.drawStatusBar('
|
|
120
|
+
TUI.drawStatusBar('Swarm Active', options.model);
|
|
170
121
|
}
|
|
171
|
-
|
|
172
|
-
buffer += chunk;
|
|
173
122
|
fullResponse += chunk;
|
|
123
|
+
buffer += chunk;
|
|
174
124
|
const lines = buffer.split('\n');
|
|
175
125
|
buffer = lines.pop() || '';
|
|
176
126
|
|
|
177
127
|
for (const line of lines) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
128
|
+
const codeMatch = line.match(/```(\w+)?/);
|
|
129
|
+
const fileMatch = line.match(/<file path="([^"]+)">/);
|
|
130
|
+
const patchMatch = line.match(/<<<< SEARCH/);
|
|
131
|
+
const sumMatch = line.match(/<summary>/);
|
|
132
|
+
|
|
133
|
+
if (!technicalType && (codeMatch || fileMatch || patchMatch || sumMatch)) {
|
|
134
|
+
if (codeMatch) technicalType = 'code', codeLang = codeMatch[1] || 'bash';
|
|
135
|
+
else if (fileMatch) technicalType = 'file';
|
|
136
|
+
else if (patchMatch) technicalType = 'patch';
|
|
137
|
+
else if (sumMatch) technicalType = 'summary';
|
|
138
|
+
process.stdout.write(chalk.gray(`\n [⚡] Swarm processing \${technicalType}...\n`));
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
189
141
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
142
|
+
const isEnd = (technicalType === 'code' && line.trim() === '```') ||
|
|
143
|
+
(technicalType === 'file' && line.includes('</file>')) ||
|
|
144
|
+
(technicalType === 'patch' && line.includes('>>>> REPLACE')) ||
|
|
145
|
+
(technicalType === 'summary' && line.includes('</summary>'));
|
|
146
|
+
|
|
147
|
+
if (isEnd) {
|
|
148
|
+
const content = technicalBuffer.trim();
|
|
149
|
+
if (technicalType === 'summary') TUI.drawSummaryBox(content);
|
|
150
|
+
else TUI.drawCodeContainer(technicalType || 'Technical Block', codeLang, content);
|
|
151
|
+
technicalBuffer = '';
|
|
152
|
+
technicalType = null;
|
|
153
|
+
process.stdout.write('\n');
|
|
154
|
+
continue;
|
|
196
155
|
}
|
|
197
156
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
inCodeBlock = !inCodeBlock;
|
|
201
|
-
if (inCodeBlock && options.execute) {
|
|
202
|
-
console.log(chalk.gray('[Technical Block - Executing in Terminal...]'));
|
|
203
|
-
}
|
|
157
|
+
if (technicalType) {
|
|
158
|
+
technicalBuffer += line + '\n';
|
|
204
159
|
continue;
|
|
205
160
|
}
|
|
206
161
|
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
162
|
+
const agentMatch = line.match(/(?:\[\**\s*|\b)(Ajay Vai|Sandip Dai|Sunil Dai|Bishal Dai|Narayan Dai)\s*\**\]?[:\s]*/i);
|
|
163
|
+
if (agentMatch) {
|
|
164
|
+
const name = agentMatch[1];
|
|
165
|
+
const content = line.replace(agentMatch[0], '').replace(/\*{2,4}/g, '').trim();
|
|
166
|
+
if (name.toLowerCase() === 'ajay vai') {
|
|
167
|
+
process.stdout.write('\n' + chalk.magenta.bold('[' + name + ']:') + ' ');
|
|
168
|
+
if (content) process.stdout.write(chalk.gray(content + ' '));
|
|
169
|
+
} else {
|
|
170
|
+
if (content) TUI.drawSwarmDialogue(name, content);
|
|
211
171
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Banter / Chat
|
|
215
|
-
console.log(chalk.gray(content));
|
|
172
|
+
} else if (line.trim()) {
|
|
173
|
+
process.stdout.write(chalk.gray(line.trim() + ' '));
|
|
216
174
|
}
|
|
217
175
|
}
|
|
218
176
|
}, abortController.signal);
|
|
219
|
-
} catch (
|
|
220
|
-
if (isAborted
|
|
221
|
-
|
|
222
|
-
} else {
|
|
223
|
-
throw streamErr;
|
|
224
|
-
}
|
|
177
|
+
} catch (e: any) {
|
|
178
|
+
if (!isAborted && e.name !== 'AbortError') throw e;
|
|
179
|
+
console.log(chalk.gray('\n [🛑] Stopped.'));
|
|
225
180
|
} finally {
|
|
226
|
-
// RESTORE TERMINAL
|
|
227
181
|
process.stdin.removeListener('data', onData);
|
|
228
182
|
if (process.stdin.setRawMode) process.stdin.setRawMode(isRaw);
|
|
229
183
|
process.stdin.pause();
|
|
184
|
+
spinner.stop();
|
|
230
185
|
}
|
|
231
186
|
|
|
232
|
-
spinner.stop();
|
|
233
|
-
|
|
234
|
-
if (buffer.trim() && !inCodeBlock && !isAborted) {
|
|
235
|
-
console.log(chalk.gray(buffer.trim()));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Add assistant response to history
|
|
239
187
|
messages.push({ role: 'assistant', content: fullResponse });
|
|
240
|
-
const response = fullResponse;
|
|
241
188
|
console.log();
|
|
242
189
|
|
|
243
|
-
// Execute commands if requested
|
|
244
190
|
if (options.execute) {
|
|
245
191
|
const { executeWithPermission } = await import('../utils/command-executor');
|
|
246
|
-
const result = await executeWithPermission(
|
|
247
|
-
|
|
248
|
-
// Update session CWD from result
|
|
192
|
+
const result = await executeWithPermission(fullResponse, currentSessionCwd);
|
|
249
193
|
if (result.newCwd) {
|
|
250
194
|
currentSessionCwd = result.newCwd;
|
|
251
|
-
|
|
252
|
-
messages[0].content = messages[0].content.replace(/ABSOLUTE WORKING DIRECTORY: .*/, `ABSOLUTE WORKING DIRECTORY: ${currentSessionCwd}`);
|
|
195
|
+
messages[0].content = messages[0].content.replace(/ABSOLUTE WORKING DIRECTORY: .*/, `ABSOLUTE WORKING DIRECTORY: \${currentSessionCwd}`);
|
|
253
196
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
// Success! Feed output back to AI for next step
|
|
258
|
-
console.log(chalk.gray('↺ Feeding output to AI...'));
|
|
259
|
-
messages.push({
|
|
260
|
-
role: 'user',
|
|
261
|
-
content: `✅ Command executed successfully in ${currentSessionCwd}. Output:\n${result.output}\n\nProceed to the next step.`
|
|
262
|
-
});
|
|
263
|
-
continue;
|
|
264
|
-
} else {
|
|
265
|
-
console.log(chalk.yellow('\n↺ Command failed. Asking AI to fix...'));
|
|
266
|
-
messages.push({
|
|
267
|
-
role: 'user',
|
|
268
|
-
content: `❌ Command failed with error:\n${result.error}\nPlease fix this. Current CWD is ${currentSessionCwd}.`
|
|
269
|
-
});
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
} else {
|
|
273
|
-
break;
|
|
197
|
+
if (result.executed && result.success) {
|
|
198
|
+
messages.push({ role: 'user', content: `✅ Command success. Output:\n\${result.output}` });
|
|
199
|
+
continue;
|
|
274
200
|
}
|
|
275
|
-
} else {
|
|
276
|
-
break;
|
|
277
201
|
}
|
|
278
|
-
|
|
202
|
+
break;
|
|
279
203
|
} catch (error: any) {
|
|
280
|
-
spinner.fail('
|
|
281
|
-
|
|
282
|
-
console.error(chalk.red('❌ Invalid or revoked API key.'));
|
|
283
|
-
process.exit(1);
|
|
284
|
-
} else if (error.message.includes('429')) {
|
|
285
|
-
console.error(chalk.red('❌ Rate limit exceeded. Please wait a moment.'));
|
|
286
|
-
} else {
|
|
287
|
-
console.error(chalk.red(`❌ Error: ${error.message}`));
|
|
288
|
-
}
|
|
289
|
-
messages.pop(); // Remove failed user message
|
|
204
|
+
spinner.fail('Swarm error');
|
|
205
|
+
console.error(chalk.red(`❌ Error: \${error.message}`));
|
|
290
206
|
break;
|
|
291
207
|
}
|
|
292
208
|
}
|
|
293
209
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
console.error(chalk.red(`\n❌ Chat session error: ${error.message}`));
|
|
210
|
+
} catch (outerError: any) {
|
|
211
|
+
console.error(chalk.red(`\n❌ Session error: \${outerError.message}`));
|
|
297
212
|
process.exit(1);
|
|
298
213
|
}
|
|
299
214
|
});
|
package/src/commands/dev.ts
CHANGED
|
@@ -159,10 +159,14 @@ If a file is too large to read entirely (e.g., thousands of lines):
|
|
|
159
159
|
|
|
160
160
|
const abortController = new AbortController();
|
|
161
161
|
let isAborted = false;
|
|
162
|
+
const streamStartTime = Date.now();
|
|
162
163
|
|
|
163
164
|
// LISTEN FOR INTERRUPTION (Enter, Escape, Ctrl+C)
|
|
164
165
|
const onData = (data: Buffer) => {
|
|
165
|
-
//
|
|
166
|
+
// Check for Enter (13), Newline (10), Escape (27), or Ctrl+C (3)
|
|
167
|
+
// 🏁 GRACE PERIOD: Ignore aborts in the first 200ms to prevent stray newlines
|
|
168
|
+
if (Date.now() - streamStartTime < 200) return;
|
|
169
|
+
|
|
166
170
|
if (data[0] === 13 || data[0] === 10 || data[0] === 27 || data[0] === 3) {
|
|
167
171
|
isAborted = true;
|
|
168
172
|
abortController.abort();
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { configManager } from './utils/config';
|
|
|
4
4
|
import { MatexAPIClient, ChatMessage } from './api/client';
|
|
5
5
|
import { spinner } from './utils/spinner';
|
|
6
6
|
import { devCommand } from './commands/dev';
|
|
7
|
+
import { chatCommand } from './commands/chat';
|
|
7
8
|
import { helpCommand } from './commands/help';
|
|
8
9
|
import { TUI } from './utils/tui';
|
|
9
10
|
|
|
@@ -18,6 +19,7 @@ program
|
|
|
18
19
|
|
|
19
20
|
// Add commands
|
|
20
21
|
program.addCommand(devCommand);
|
|
22
|
+
program.addCommand(chatCommand);
|
|
21
23
|
program.addCommand(helpCommand);
|
|
22
24
|
|
|
23
25
|
// Config commands
|
|
@@ -119,11 +121,21 @@ ${context}`
|
|
|
119
121
|
];
|
|
120
122
|
|
|
121
123
|
let fullResponse = '';
|
|
124
|
+
let buffer = '';
|
|
125
|
+
let technicalBuffer = '';
|
|
126
|
+
let technicalType: 'code' | 'file' | 'patch' | 'summary' | null = null;
|
|
127
|
+
let codeLang = 'bash';
|
|
128
|
+
let hasStarted = false;
|
|
129
|
+
|
|
122
130
|
const abortController = new AbortController();
|
|
123
131
|
let isAborted = false;
|
|
132
|
+
const streamStartTime = Date.now();
|
|
124
133
|
|
|
125
134
|
const onData = (data: Buffer) => {
|
|
126
|
-
|
|
135
|
+
// 🏁 GRACE PERIOD: Ignore aborts in the first 200ms to prevent stray newlines
|
|
136
|
+
if (Date.now() - streamStartTime < 200) return;
|
|
137
|
+
|
|
138
|
+
if (!isAborted && (data[0] === 13 || data[0] === 10 || data[0] === 27 || data[0] === 3)) {
|
|
127
139
|
isAborted = true;
|
|
128
140
|
abortController.abort();
|
|
129
141
|
}
|
|
@@ -139,13 +151,94 @@ ${context}`
|
|
|
139
151
|
messages,
|
|
140
152
|
model: options.model,
|
|
141
153
|
}, (chunk) => {
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
if (!hasStarted) {
|
|
155
|
+
spinner.stop();
|
|
156
|
+
hasStarted = true;
|
|
157
|
+
}
|
|
144
158
|
fullResponse += chunk;
|
|
159
|
+
buffer += chunk;
|
|
160
|
+
|
|
161
|
+
const lines = buffer.split('\n');
|
|
162
|
+
buffer = lines.pop() || '';
|
|
163
|
+
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
// 1. Technical Block Detection
|
|
166
|
+
const codeBlockMatch = line.match(/```(\w+)?/);
|
|
167
|
+
const fileStartMatch = line.match(/<file path="([^"]+)">/);
|
|
168
|
+
const patchStartMatch = line.match(/<<<< SEARCH/);
|
|
169
|
+
const summaryStartMatch = line.match(/<summary>/);
|
|
170
|
+
|
|
171
|
+
if (!technicalType && (codeBlockMatch || fileStartMatch || patchStartMatch || summaryStartMatch)) {
|
|
172
|
+
if (codeBlockMatch) {
|
|
173
|
+
technicalType = 'code';
|
|
174
|
+
codeLang = codeBlockMatch[1] || 'bash';
|
|
175
|
+
process.stdout.write(chalk.gray('\n [⚡] Building technical block...\n'));
|
|
176
|
+
} else if (fileStartMatch) {
|
|
177
|
+
technicalType = 'file';
|
|
178
|
+
process.stdout.write(chalk.cyan(`\n [📂] Creating file: ${fileStartMatch[1]}...\n`));
|
|
179
|
+
} else if (patchStartMatch) {
|
|
180
|
+
technicalType = 'patch';
|
|
181
|
+
process.stdout.write(chalk.yellow('\n [📂] Applying surgical patch...\n'));
|
|
182
|
+
} else if (summaryStartMatch) {
|
|
183
|
+
technicalType = 'summary';
|
|
184
|
+
process.stdout.write(chalk.magenta('\n [📝] Generating Ajay\'s Work Summary...\n'));
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 2. Technical Block End Detection
|
|
190
|
+
const fileEndMatch = line.match(/<\/file>/);
|
|
191
|
+
const patchEndMatch = line.match(/>>>> REPLACE/);
|
|
192
|
+
const summaryEndMatch = line.match(/<\/summary>/);
|
|
193
|
+
const isCodeEnd = technicalType === 'code' && line.trim() === '```';
|
|
194
|
+
|
|
195
|
+
if (isCodeEnd || fileEndMatch || patchEndMatch || summaryEndMatch) {
|
|
196
|
+
const displayContent = technicalBuffer.trim();
|
|
197
|
+
if (technicalType === 'summary') {
|
|
198
|
+
TUI.drawSummaryBox(displayContent);
|
|
199
|
+
} else {
|
|
200
|
+
TUI.drawCodeContainer(
|
|
201
|
+
technicalType === 'file' ? 'New File Content' :
|
|
202
|
+
technicalType === 'patch' ? 'Surgical Patch' : 'Generated Block',
|
|
203
|
+
technicalType === 'code' ? codeLang : 'text',
|
|
204
|
+
displayContent
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
technicalBuffer = '';
|
|
208
|
+
technicalType = null;
|
|
209
|
+
process.stdout.write('\n');
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 3. Content Handling
|
|
214
|
+
if (technicalType) {
|
|
215
|
+
technicalBuffer += line + '\n';
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Agent Detection & Dialogue Printing
|
|
220
|
+
const agentMatch = line.match(/(?:\[\**\s*|\b)(Ajay Vai|Sandip Dai|Sunil Dai|Bishal Dai|Narayan Dai)\s*\**\]?[:\s]*/i);
|
|
221
|
+
if (agentMatch) {
|
|
222
|
+
const agentName = agentMatch[1];
|
|
223
|
+
let content = line.replace(/(?:\[\**\s*|\b)(Ajay Vai|Sandip Dai|Sunil Dai|Bishal Dai|Narayan Dai)\s*\**\]?[:\s]*/i, '').trim();
|
|
224
|
+
content = content.replace(/\*{2,4}/g, '').trim();
|
|
225
|
+
|
|
226
|
+
if (agentName.toLowerCase() === 'ajay vai') {
|
|
227
|
+
const color = chalk.magenta;
|
|
228
|
+
process.stdout.write(`\n${color.bold(`[${agentName}]:`)} `);
|
|
229
|
+
if (content) process.stdout.write(chalk.gray(content + ' '));
|
|
230
|
+
} else {
|
|
231
|
+
if (content) TUI.drawSwarmDialogue(agentName, content);
|
|
232
|
+
}
|
|
233
|
+
} else if (line.trim()) {
|
|
234
|
+
process.stdout.write(chalk.gray(line.trim() + ' '));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
145
237
|
}, abortController.signal);
|
|
146
238
|
} catch (streamErr: any) {
|
|
147
239
|
if (isAborted || streamErr.name === 'CanceledError' || streamErr.message === 'canceled') {
|
|
148
240
|
console.log(chalk.gray('\n\n [🛑] Swarm stopped by brother (Enter pressed).'));
|
|
241
|
+
if (!hasStarted) spinner.stop();
|
|
149
242
|
} else {
|
|
150
243
|
throw streamErr;
|
|
151
244
|
}
|
|
@@ -142,10 +142,14 @@ export async function executeCommand(command: string, shell?: string, cwd?: stri
|
|
|
142
142
|
let stdout = '';
|
|
143
143
|
let stderr = '';
|
|
144
144
|
let isAborted = false;
|
|
145
|
+
const commandStartTime = Date.now();
|
|
145
146
|
|
|
146
147
|
// Listen for "Enter" or "Escape" to kill the command
|
|
147
148
|
const onData = (data: Buffer) => {
|
|
148
149
|
// Check for Enter (13), Newline (10), Escape (27), or Ctrl+C (3)
|
|
150
|
+
// 🏁 GRACE PERIOD: Ignore aborts in the first 200ms to prevent stray newlines
|
|
151
|
+
if (Date.now() - commandStartTime < 200) return;
|
|
152
|
+
|
|
149
153
|
if (data[0] === 13 || data[0] === 10 || data[0] === 27 || data[0] === 3) {
|
|
150
154
|
isAborted = true;
|
|
151
155
|
child.kill('SIGINT'); // Send SIGINT to gracefully terminate
|
package/src/utils/repo-mapper.ts
CHANGED
|
@@ -13,8 +13,11 @@ export class RepoMapper {
|
|
|
13
13
|
private rootPath: string;
|
|
14
14
|
private ignoreList: string[] = [
|
|
15
15
|
'.git', 'node_modules', 'dist', 'build', '.next', '.DS_Store',
|
|
16
|
-
'coverage', '.vercel', '.firebase', 'out', 'public'
|
|
16
|
+
'coverage', '.vercel', '.firebase', 'out', 'public',
|
|
17
|
+
'bin', 'obj', '.vs', 'vendor', '__pycache__', 'env', '.env', 'venv'
|
|
17
18
|
];
|
|
19
|
+
private fileCount = 0;
|
|
20
|
+
private readonly MAX_FILES = 500;
|
|
18
21
|
private fileContents: Map<string, string> = new Map();
|
|
19
22
|
|
|
20
23
|
constructor(rootPath: string) {
|
|
@@ -100,6 +103,7 @@ export class RepoMapper {
|
|
|
100
103
|
try {
|
|
101
104
|
const items = fs.readdirSync(currentPath);
|
|
102
105
|
for (const item of items) {
|
|
106
|
+
if (this.fileCount > this.MAX_FILES) break;
|
|
103
107
|
if (this.ignoreList.includes(item)) continue;
|
|
104
108
|
|
|
105
109
|
const fullPath = path.join(currentPath, item);
|
package/src/utils/tui.ts
CHANGED
|
@@ -76,6 +76,9 @@ export class TUI {
|
|
|
76
76
|
const width = process.stdout.columns || 80;
|
|
77
77
|
const height = process.stdout.rows || 24;
|
|
78
78
|
|
|
79
|
+
// 🛡️ WINDOWS SAFETY: If terminal size is invalid, skip status bar to prevent crashes
|
|
80
|
+
if (width <= 0 || height <= 0) return;
|
|
81
|
+
|
|
79
82
|
const leftTag = chalk.bgHex('#1E1E1E').hex('#D97757').bold(' ⚡ MATEX AI ');
|
|
80
83
|
const modelTag = chalk.bgHex('#333333').white(` 🤖 ${model} `);
|
|
81
84
|
const cwdTag = chalk.bgHex('#1E1E1E').gray(` 📂 ${path.basename(process.cwd())} `);
|
|
@@ -84,10 +87,14 @@ export class TUI {
|
|
|
84
87
|
const remainingWidth = width - (12 + modelTag.length + cwdTag.length + mainMessage.length);
|
|
85
88
|
const spacer = chalk.bgHex('#1E1E1E')(' '.repeat(Math.max(0, remainingWidth)));
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
try {
|
|
91
|
+
process.stdout.write('\x1b[s');
|
|
92
|
+
readline.cursorTo(process.stdout, 0, height - 1);
|
|
93
|
+
process.stdout.write(leftTag + mainMessage + spacer + modelTag + cwdTag);
|
|
94
|
+
process.stdout.write('\x1b[u');
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// Silently fail for TUI errors on unstable terminals
|
|
97
|
+
}
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
/**
|