matex-cli 1.2.81 → 1.2.84

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 (142) hide show
  1. package/dist/commands/chaos.js +3 -8
  2. package/dist/prompts/chaos-prompts.js +26 -21
  3. package/dist/session/agent-session.js +38 -9
  4. package/dist/utils/agent-orchestrator.js +7 -0
  5. package/dist/utils/mcp-server.js +6 -0
  6. package/dist/utils/tui.js +84 -7
  7. package/package.json +7 -1
  8. package/.agents/skills/mcp-server-dev/SKILL.md +0 -60
  9. package/.agents/skills/mcp-server-dev/examples/basic-ts-server/package.json +0 -20
  10. package/.agents/skills/mcp-server-dev/examples/basic-ts-server/src/index.ts +0 -66
  11. package/.agents/skills/mcp-server-dev/resources/best_practices.md +0 -20
  12. package/.agents/skills/pptx-presentation-builder/SKILL.md +0 -338
  13. package/.agents/workflows/deploy.md +0 -27
  14. package/dist/api/client.d.ts +0 -40
  15. package/dist/api/client.d.ts.map +0 -1
  16. package/dist/api/client.js.map +0 -1
  17. package/dist/commands/ask.d.ts +0 -3
  18. package/dist/commands/ask.d.ts.map +0 -1
  19. package/dist/commands/ask.js.map +0 -1
  20. package/dist/commands/augov.d.ts +0 -3
  21. package/dist/commands/augov.d.ts.map +0 -1
  22. package/dist/commands/augov.js.map +0 -1
  23. package/dist/commands/bro.d.ts +0 -4
  24. package/dist/commands/bro.d.ts.map +0 -1
  25. package/dist/commands/bro.js.map +0 -1
  26. package/dist/commands/chaos.d.ts +0 -3
  27. package/dist/commands/chaos.d.ts.map +0 -1
  28. package/dist/commands/chaos.js.map +0 -1
  29. package/dist/commands/chat.d.ts +0 -3
  30. package/dist/commands/chat.d.ts.map +0 -1
  31. package/dist/commands/chat.js.map +0 -1
  32. package/dist/commands/code.d.ts +0 -3
  33. package/dist/commands/code.d.ts.map +0 -1
  34. package/dist/commands/code.js.map +0 -1
  35. package/dist/commands/config.d.ts +0 -3
  36. package/dist/commands/config.d.ts.map +0 -1
  37. package/dist/commands/config.js.map +0 -1
  38. package/dist/commands/dev.d.ts +0 -3
  39. package/dist/commands/dev.d.ts.map +0 -1
  40. package/dist/commands/dev.js.map +0 -1
  41. package/dist/commands/help.d.ts +0 -3
  42. package/dist/commands/help.d.ts.map +0 -1
  43. package/dist/commands/help.js.map +0 -1
  44. package/dist/commands/login.d.ts +0 -3
  45. package/dist/commands/login.d.ts.map +0 -1
  46. package/dist/commands/login.js.map +0 -1
  47. package/dist/commands/models.d.ts +0 -3
  48. package/dist/commands/models.d.ts.map +0 -1
  49. package/dist/commands/models.js.map +0 -1
  50. package/dist/commands/student.d.ts +0 -3
  51. package/dist/commands/student.d.ts.map +0 -1
  52. package/dist/commands/student.js.map +0 -1
  53. package/dist/commands/study.d.ts +0 -3
  54. package/dist/commands/study.d.ts.map +0 -1
  55. package/dist/commands/study.js.map +0 -1
  56. package/dist/index.d.ts +0 -3
  57. package/dist/index.d.ts.map +0 -1
  58. package/dist/index.js.map +0 -1
  59. package/dist/prompts/banter-augov.d.ts +0 -2
  60. package/dist/prompts/banter-augov.d.ts.map +0 -1
  61. package/dist/prompts/banter-augov.js.map +0 -1
  62. package/dist/prompts/banter.d.ts +0 -6
  63. package/dist/prompts/banter.d.ts.map +0 -1
  64. package/dist/prompts/banter.js.map +0 -1
  65. package/dist/prompts/chaos-prompts.d.ts +0 -3
  66. package/dist/prompts/chaos-prompts.d.ts.map +0 -1
  67. package/dist/prompts/chaos-prompts.js.map +0 -1
  68. package/dist/prompts/system-prompts.d.ts +0 -4
  69. package/dist/prompts/system-prompts.d.ts.map +0 -1
  70. package/dist/prompts/system-prompts.js.map +0 -1
  71. package/dist/session/agent-session.d.ts +0 -41
  72. package/dist/session/agent-session.d.ts.map +0 -1
  73. package/dist/session/agent-session.js.map +0 -1
  74. package/dist/utils/agent-orchestrator.d.ts +0 -44
  75. package/dist/utils/agent-orchestrator.d.ts.map +0 -1
  76. package/dist/utils/agent-orchestrator.js.map +0 -1
  77. package/dist/utils/augov-logger.d.ts +0 -11
  78. package/dist/utils/augov-logger.d.ts.map +0 -1
  79. package/dist/utils/augov-logger.js.map +0 -1
  80. package/dist/utils/augov-scrubber.d.ts +0 -23
  81. package/dist/utils/augov-scrubber.d.ts.map +0 -1
  82. package/dist/utils/augov-scrubber.js.map +0 -1
  83. package/dist/utils/command-executor.d.ts +0 -56
  84. package/dist/utils/command-executor.d.ts.map +0 -1
  85. package/dist/utils/command-executor.js.map +0 -1
  86. package/dist/utils/config.d.ts +0 -56
  87. package/dist/utils/config.d.ts.map +0 -1
  88. package/dist/utils/config.js.map +0 -1
  89. package/dist/utils/mcp-server.d.ts +0 -77
  90. package/dist/utils/mcp-server.d.ts.map +0 -1
  91. package/dist/utils/mcp-server.js.map +0 -1
  92. package/dist/utils/patcher.d.ts +0 -45
  93. package/dist/utils/patcher.d.ts.map +0 -1
  94. package/dist/utils/patcher.js.map +0 -1
  95. package/dist/utils/repo-mapper.d.ts +0 -32
  96. package/dist/utils/repo-mapper.d.ts.map +0 -1
  97. package/dist/utils/repo-mapper.js.map +0 -1
  98. package/dist/utils/spinner.d.ts +0 -15
  99. package/dist/utils/spinner.d.ts.map +0 -1
  100. package/dist/utils/spinner.js.map +0 -1
  101. package/dist/utils/tui.d.ts +0 -134
  102. package/dist/utils/tui.d.ts.map +0 -1
  103. package/dist/utils/tui.js.map +0 -1
  104. package/fix-npm-permissions.sh +0 -23
  105. package/publish-local.sh +0 -16
  106. package/skills-lock.json +0 -10
  107. package/src/api/client.ts +0 -197
  108. package/src/commands/ask.ts +0 -62
  109. package/src/commands/augov.ts +0 -301
  110. package/src/commands/bro.ts +0 -336
  111. package/src/commands/chaos.ts +0 -67
  112. package/src/commands/chat.ts +0 -61
  113. package/src/commands/code.ts +0 -99
  114. package/src/commands/config.ts +0 -78
  115. package/src/commands/dev.ts +0 -68
  116. package/src/commands/help.ts +0 -67
  117. package/src/commands/login.ts +0 -47
  118. package/src/commands/models.ts +0 -81
  119. package/src/commands/student.ts +0 -23
  120. package/src/commands/study.ts +0 -75
  121. package/src/index.ts +0 -284
  122. package/src/prompts/banter-augov.ts +0 -17
  123. package/src/prompts/banter.ts +0 -101
  124. package/src/prompts/chaos-prompts.ts +0 -48
  125. package/src/prompts/system-prompts.ts +0 -145
  126. package/src/session/agent-session.ts +0 -428
  127. package/src/utils/agent-orchestrator.ts +0 -264
  128. package/src/utils/augov-logger.ts +0 -34
  129. package/src/utils/augov-scrubber.ts +0 -67
  130. package/src/utils/command-executor.ts +0 -529
  131. package/src/utils/config.ts +0 -124
  132. package/src/utils/mcp-server.ts +0 -388
  133. package/src/utils/patcher.ts +0 -229
  134. package/src/utils/repo-mapper.ts +0 -198
  135. package/src/utils/spinner.ts +0 -66
  136. package/src/utils/tui.ts +0 -749
  137. package/test-chaos-container.js +0 -16
  138. package/test-chaos-fix.js +0 -18
  139. package/test-config.ts +0 -2
  140. package/test-ui-output.js +0 -16
  141. package/tsconfig.json +0 -27
  142. package/vertex_ai_agent.py +0 -52
@@ -1,34 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- /**
5
- * IRAP-Compliant Audit Logger
6
- * The Australian Government requires strict logging of AI tool usage.
7
- * This logger records prompts and actions locally so security teams can audit them.
8
- */
9
- export class GovAuditLogger {
10
- private logFile: string;
11
-
12
- constructor(baseDir: string = process.cwd()) {
13
- const logDir = path.join(baseDir, '.matex_audit');
14
- if (!fs.existsSync(logDir)) {
15
- fs.mkdirSync(logDir, { recursive: true });
16
- }
17
-
18
- // Create an auditable log per day
19
- const today = new Date().toISOString().split('T')[0];
20
- this.logFile = path.join(logDir, `augov_audit_${today}.log`);
21
-
22
- if (!fs.existsSync(this.logFile)) {
23
- fs.writeFileSync(this.logFile, 'TIMESTAMP | ROLE | ACTION | CONTENT\n');
24
- }
25
- }
26
-
27
- public logInteraction(role: 'USER' | 'AI_SWARM' | 'SYSTEM', action: string, content: string) {
28
- const timestamp = new Date().toISOString();
29
- const cleanContent = content.replace(/\n/g, ' ').substring(0, 500); // truncate for CSV safety, full logs can be large
30
- const logEntry = `${timestamp} | ${role} | ${action} | ${cleanContent}\n`;
31
-
32
- fs.appendFileSync(this.logFile, logEntry);
33
- }
34
- }
@@ -1,67 +0,0 @@
1
- import chalk from 'chalk';
2
-
3
- /**
4
- * Citizen Privacy Shield
5
- * A local redaction engine that scrubs Australian PII (Personally Identifiable Information)
6
- * BEFORE it ever leaves the user's computer to hit an AI API.
7
- */
8
- export class GovPrivacyScrubber {
9
-
10
- // Regex patterns for Australian specific sensitive data
11
- private static patterns = [
12
- // Australian Tax File Number (TFN) - 9 digits
13
- { regex: /\b\d{3}[- ]?\d{3}[- ]?\d{3}\b/g, replacement: '[REDACTED_TFN]' },
14
- // Medicare Number - 10 digits
15
- { regex: /\b[2-6]\d{9}\b/g, replacement: '[REDACTED_MEDICARE]' },
16
- // Australian Phone Numbers (Mobile & Landline)
17
- { regex: /(?:\+?61|0)[2-478](?:[ -]?[0-9]){8}\b/g, replacement: '[REDACTED_PHONE]' },
18
- // Email Addresses
19
- { regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, replacement: '[REDACTED_EMAIL]' },
20
- // Credit Card Numbers
21
- { regex: /\b(?:\d[ -]*?){13,16}\b/g, replacement: '[REDACTED_CREDIT_CARD]' },
22
-
23
- // --- FOI (Freedom of Information) Specific Redactions ---
24
-
25
- // Cabinet in Confidence Markers
26
- { regex: /Cabinet in Confidence/ig, replacement: '[REDACTED_CABINET_MATERIAL]' },
27
- { regex: /National Cabinet/ig, replacement: '[REDACTED_NATIONAL_CABINET_MATERIAL]' },
28
- // Highly Classified Clearance Words
29
- { regex: /TOP SECRET/ig, replacement: '[REDACTED_CLASSIFICATION]' },
30
- // Military Base Locations (Example subset for ADF)
31
- { regex: /HMAS Stirling|RAAF Base Tindal|Pine Gap|Russell Offices/ig, replacement: '[REDACTED_DEFENCE_ASSET]' },
32
- // Diplomatic Identifiers
33
- { regex: /Ambassador [A-Z][a-z]+/ig, replacement: '[REDACTED_DIPLOMAT]' },
34
- { regex: /Five Eyes|AUKUS/ig, replacement: '[REDACTED_INTELLIGENCE_ALLIANCE]' }
35
- ];
36
-
37
- /**
38
- * Scrubs sensitive data from a string.
39
- * @param input Raw string from user or file
40
- * @returns Scrubbed string safe for AI processing
41
- */
42
- public static scrub(input: string): string {
43
- let scrubbed = input;
44
- for (const rule of this.patterns) {
45
- scrubbed = scrubbed.replace(rule.regex, rule.replacement);
46
- }
47
- return scrubbed;
48
- }
49
-
50
- /**
51
- * Specialized FOI Document Scrubber
52
- * Used exclusively for the `matex augov redact` command
53
- */
54
- public static async redactDocumentContent(content: string): Promise<{ scrubbed: string, redactionCount: number }> {
55
- let scrubbed = content;
56
- let redactionCount = 0;
57
-
58
- for (const rule of this.patterns) {
59
- const matches = content.match(rule.regex);
60
- if (matches) {
61
- redactionCount += matches.length;
62
- scrubbed = scrubbed.replace(rule.regex, chalk.bgBlack.white(rule.replacement));
63
- }
64
- }
65
- return { scrubbed, redactionCount };
66
- }
67
- }
@@ -1,529 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import chalk from 'chalk';
4
- import { exec, spawn } from 'child_process';
5
- import { promisify } from 'util';
6
- import inquirer from 'inquirer';
7
- import { TUI } from './tui';
8
- import { AgentOrchestrator } from './agent-orchestrator';
9
- import { Patcher } from './patcher';
10
-
11
- const execAsync = promisify(exec);
12
-
13
- export interface CommandBlock {
14
- language: string;
15
- code: string;
16
- dangerous: boolean;
17
- description?: string;
18
- }
19
-
20
- /**
21
- * Extract executable commands from AI response
22
- */
23
- export function extractCommands(response: string): CommandBlock[] {
24
- const commands: CommandBlock[] = [];
25
-
26
- // Match code blocks with language tags (case-insensitive)
27
- const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/gi;
28
- let match;
29
-
30
- while ((match = codeBlockRegex.exec(response)) !== null) {
31
- const language = match[1] || 'bash';
32
- const code = match[2].trim();
33
-
34
- // 🛡️ HEURISTIC: Reject directory trees formatted as "commands"
35
- const isTree = /├──|└──|│\s+├──/.test(code);
36
- if (isTree) continue;
37
-
38
- // Only extract shell commands
39
- if (['bash', 'sh', 'zsh', 'shell', 'powershell', 'cmd'].includes(language.toLowerCase())) {
40
- // 🛡️ DIALOGUE FILTER: Strip lines that look like agent banter [Ajay Vai]: etc.
41
- const lines = code.split('\n');
42
- const filteredLines = lines.filter(line => {
43
- const trimmed = line.trim();
44
- // If it starts with [Agent Name] or typical agent markers, it's NOT a command
45
- const isAgent = /^(?:\[\**\s*|\b)(Ajay|Sunil|Sandip|Bishal|Narayan|Big Bro)\s*(?:Vai|Dai)?\s*\**\]?[:\s]*/i.test(trimmed);
46
- return !isAgent;
47
- });
48
-
49
- const filteredCode = filteredLines.join('\n').trim();
50
- if (filteredCode) {
51
- commands.push({
52
- language,
53
- code: filteredCode,
54
- dangerous: isDangerousCommand(filteredCode)
55
- });
56
- }
57
- }
58
- }
59
-
60
- return commands;
61
- }
62
-
63
- /**
64
- * Check if command is potentially dangerous
65
- */
66
- function isDangerousCommand(command: string): boolean {
67
- const dangerousPatterns = [
68
- /rm\s+-rf\s+\//, // rm -rf /
69
- /rm\s+-rf\s+~/, // rm -rf ~
70
- /rm\s+-rf\s+\*/, // rm -rf *
71
- /:\(\)\{.*\}/, // Fork bomb
72
- /dd\s+if=.*of=\/dev/, // Disk operations
73
- /mkfs/, // Format filesystem
74
- /fdisk/, // Partition operations
75
- />\s*\/dev\/sd/, // Write to disk
76
- /chmod\s+-R\s+777/, // Dangerous permissions
77
- /curl.*\|\s*bash/, // Pipe to bash
78
- /wget.*\|\s*sh/, // Pipe to shell
79
- /sudo\s+rm/, // Sudo remove
80
- /format\s+[A-Z]:/, // Windows format
81
- /del\s+\/[FS]/, // Windows delete
82
- ];
83
-
84
- return dangerousPatterns.some(pattern => pattern.test(command));
85
- }
86
-
87
- /**
88
- * Check if command is safe to auto-execute
89
- */
90
- export function isSafeAutoCommand(command: string): boolean {
91
- const safePrefixes = [
92
- // File Discovery & Read
93
- 'cat ', 'ls ', 'grep ', 'find ', 'pwd', 'echo ', 'read ', 'type ', 'dir',
94
- // File Operations
95
- 'mkdir ', 'touch ', 'cp ', 'mv ', 'rm ', 'del ', 'copy ', 'move ',
96
- // Dev Tools
97
- 'npm ', 'npx ', 'git ', 'node ', 'python ', 'python3 ', 'pip ', 'pip3 ',
98
- // Cloud & Mobile
99
- 'firebase ', 'gcloud ', 'docker ', 'flutter ', 'react-native ',
100
- // Build & Package
101
- 'cd ', 'tsc ', 'vite ', 'next ', 'cargo ', 'go ', 'swift '
102
- ];
103
- const trimmed = command.trim();
104
- return safePrefixes.some(prefix => trimmed.startsWith(prefix));
105
- }
106
-
107
- /**
108
- * Ask user for permission to execute command
109
- */
110
- export async function askPermission(command: CommandBlock): Promise<boolean> {
111
- // Clean Codex-style Prompt with Multi-Agent Iconography
112
- console.log();
113
-
114
- // Remove auto-execution logic to prevent infinite "run forever" loops
115
- // The user should always be prompted or have the option to safely step through.
116
-
117
- console.log(chalk.yellow('🛡️ SyntaxGuard ') + chalk.white('Analysis:'));
118
- console.log(chalk.gray('> ') + chalk.white(command.code));
119
- console.log();
120
-
121
- if (command.dangerous) {
122
- console.log(chalk.red('🚨 WARNING: Dangerous command'));
123
- }
124
-
125
- const { execute } = await inquirer.prompt([{
126
- type: 'confirm',
127
- name: 'execute',
128
- message: command.dangerous ?
129
- chalk.red('Execute?') :
130
- chalk.gray('Execute?'),
131
- default: true // Default to true for seamless flow (Codex style)
132
- }]);
133
-
134
- return execute;
135
- }
136
-
137
- /**
138
- * Execute a shell command with real-time streaming and interruption support
139
- */
140
- export async function executeCommand(command: string, shell?: string, cwd?: string): Promise<{ stdout: string; stderr: string; aborted?: boolean }> {
141
- return new Promise((resolve, reject) => {
142
- const shellPath = shell || process.env.SHELL || '/bin/bash';
143
- const child = spawn(shellPath, ['-c', command], {
144
- cwd: cwd || process.cwd(),
145
- env: { ...process.env, FORCE_COLOR: 'true' }
146
- });
147
-
148
- let stdout = '';
149
- let stderr = '';
150
- let isAborted = false;
151
- const commandStartTime = Date.now();
152
-
153
- // Listen for "Enter" or "Escape" to kill the command
154
- const onData = (data: Buffer) => {
155
- // Check for Enter (13), Newline (10), Escape (27), or Ctrl+C (3)
156
- // 🏁 GRACE PERIOD: Ignore aborts in the first 200ms to prevent stray newlines
157
- if (Date.now() - commandStartTime < 200) return;
158
-
159
- if (data[0] === 13 || data[0] === 10 || data[0] === 27 || data[0] === 3) {
160
- isAborted = true;
161
- child.kill('SIGINT'); // Send SIGINT to gracefully terminate
162
- }
163
- };
164
-
165
- const isRaw = process.stdin.isRaw;
166
- process.stdin.resume();
167
- if (process.stdin.setRawMode) process.stdin.setRawMode(true);
168
- process.stdin.on('data', onData);
169
-
170
- child.stdout.on('data', (data: Buffer) => {
171
- const chunk = data.toString();
172
- stdout += chunk;
173
- // No direct process.stdout.write here to prevent leaks
174
- });
175
-
176
- child.stderr.on('data', (data: Buffer) => {
177
- const chunk = data.toString();
178
- stderr += chunk;
179
- // No direct process.stderr.write here to prevent leaks
180
- });
181
-
182
- child.on('close', (code: number | null) => {
183
- // Restore terminal state
184
- process.stdin.removeListener('data', onData);
185
- if (process.stdin.setRawMode) process.stdin.setRawMode(isRaw);
186
- process.stdin.pause();
187
-
188
- if (isAborted) {
189
- resolve({ stdout, stderr, aborted: true });
190
- } else if (code === 0) {
191
- resolve({ stdout, stderr });
192
- } else {
193
- resolve({ stdout, stderr }); // Resolve instead of reject to keep agent loop alive
194
- }
195
- });
196
-
197
- child.on('error', (err: Error) => {
198
- process.stdin.removeListener('data', onData);
199
- if (process.stdin.setRawMode) process.stdin.setRawMode(isRaw);
200
- process.stdin.pause();
201
- reject(err);
202
- });
203
- });
204
- }
205
-
206
- /**
207
- * Execute commands with user permission
208
- */
209
- export interface ExecutionResult {
210
- success: boolean;
211
- executed: boolean;
212
- output?: string;
213
- error?: string;
214
- }
215
-
216
- /**
217
- * Execute commands and surgical patches with user permission
218
- */
219
- export async function executeWithPermission(response: string, currentSessionCwd?: string): Promise<ExecutionResult & { newCwd?: string }> {
220
- const commands = extractCommands(response);
221
- const patches = Patcher.parseEditBlocks(response);
222
- const files = Patcher.parseFileBlocks(response);
223
-
224
- let activeCwd = currentSessionCwd || process.cwd();
225
- let accumulatedOutput = '';
226
- let accumulatedError = '';
227
-
228
- if (commands.length === 0 && patches.length === 0 && files.length === 0) {
229
- return { success: true, executed: false, newCwd: activeCwd };
230
- }
231
-
232
- // 1. Handle New File Creation
233
- for (const file of files) {
234
- // Ensure creation happens relative to activeCwd if path is relative
235
- const originalPath = file.filePath;
236
- if (!path.isAbsolute(file.filePath)) {
237
- file.filePath = path.join(activeCwd, file.filePath);
238
- }
239
-
240
- let showFull = false;
241
- let decided = false;
242
-
243
- while (!decided) {
244
- const isTruncated = Patcher.showDiff(file, showFull);
245
-
246
- const choices = [
247
- { name: '✅ Create this new file brother', value: 'create' },
248
- { name: '⏭️ Skip', value: 'skip' }
249
- ];
250
-
251
- if (isTruncated && !showFull) {
252
- choices.splice(1, 0, { name: '🔍 View Full Code', value: 'full' });
253
- }
254
-
255
- const { action } = await inquirer.prompt([{
256
- type: 'list',
257
- name: 'action',
258
- message: chalk.cyan(`Create ${originalPath}?`),
259
- choices: choices
260
- }]);
261
-
262
- if (action === 'full') {
263
- showFull = true;
264
- continue;
265
- }
266
-
267
- if (action === 'create') {
268
- const result = Patcher.createFile(file);
269
- if (!result.success) return { success: false, executed: true, error: result.error, newCwd: activeCwd };
270
- }
271
- decided = true;
272
- }
273
- }
274
-
275
- // 2. Handle Shell Commands
276
- for (let i = 0; i < commands.length; i++) {
277
- const command = commands[i];
278
-
279
- // 🛡️ PRE-EXECUTION HALLUCINATION GUARD: Check for fake directories
280
- const commandLines = command.code.split('\n');
281
- let skipThisCommand = false;
282
- let sanitizedCode = command.code;
283
-
284
- for (const line of commandLines) {
285
- const trimmed = line.trim();
286
- // Check standalone cd commands
287
- if (trimmed.startsWith('cd ')) {
288
- let targetDir = trimmed.substring(3).replace(/['"]/g, '').trim();
289
- const potentialCwd = path.isAbsolute(targetDir) ? targetDir : path.resolve(activeCwd, targetDir);
290
-
291
- if (!fs.existsSync(potentialCwd) || !fs.statSync(potentialCwd).isDirectory()) {
292
- console.log(chalk.yellow(`\n ⚠️ Skipped hallucinated directory: "${targetDir}"`));
293
- console.log(chalk.gray(` (Does not exist at: ${potentialCwd})`));
294
- console.log(chalk.gray(` Current CWD stays: ${activeCwd}\n`));
295
- skipThisCommand = true;
296
- break;
297
- }
298
- }
299
- // Check cd inside combined commands like: cd "fake" && npm install
300
- const cdChainMatch = trimmed.match(/^cd\s+["']?([^"'&]+)["']?\s*&&/);
301
- if (cdChainMatch) {
302
- let targetDir = cdChainMatch[1].trim();
303
- const potentialCwd = path.isAbsolute(targetDir) ? targetDir : path.resolve(activeCwd, targetDir);
304
-
305
- if (!fs.existsSync(potentialCwd) || !fs.statSync(potentialCwd).isDirectory()) {
306
- console.log(chalk.yellow(`\n ⚠️ Stripping hallucinated cd from command: "${targetDir}"`));
307
- // Remove the cd part and execute the rest
308
- sanitizedCode = trimmed.replace(/^cd\s+["']?[^"'&]+["']?\s*&&\s*/, '');
309
- console.log(chalk.gray(` Running remaining: ${sanitizedCode}\n`));
310
- }
311
- }
312
- }
313
-
314
- if (skipThisCommand) continue;
315
-
316
- // Update command code with sanitized version
317
- command.code = sanitizedCode;
318
-
319
- // 🚀 MCP INTERCEPTION: If the command matches an MCP tool directly, use MCPServer
320
- const mcpMatch = command.code.trim().match(/^(web_search|read_file|write_file|list_directory|search_files|run_command|fetch_url|git_status)\s+(.*)$/);
321
-
322
- if (mcpMatch) {
323
- const toolName = mcpMatch[1];
324
- const rawArgs = mcpMatch[2].trim();
325
- console.log(chalk.cyan(`⚡ Intercepted MCP Tool: `) + chalk.white(`${toolName}(${rawArgs})`));
326
-
327
- try {
328
- const { MCPServer } = await import('./mcp-server');
329
- const mcp = new MCPServer(activeCwd);
330
-
331
- // Heuristic: If it's just a string, wrap it in a query/path object
332
- let args: any = {};
333
- if (toolName === 'web_search') args = { query: rawArgs.replace(/^['"]|['"]$/g, '') };
334
- else if (toolName === 'fetch_url') args = { url: rawArgs.replace(/^['"]|['"]$/g, '') };
335
- else if (['read_file', 'list_directory', 'git_status'].includes(toolName)) args = { path: rawArgs.replace(/^['"]|['"]$/g, '') };
336
- else args = { command: rawArgs }; // Default fallback
337
-
338
- const result = await mcp.execute(toolName, args);
339
-
340
- if (result.success) {
341
- accumulatedOutput += `[MCP ${toolName} Output]:\n${result.output}\n\n`;
342
- AgentOrchestrator.terminal(command.code, result.output);
343
- } else {
344
- accumulatedError += `[MCP ${toolName} Error]: ${result.error}\n`;
345
- AgentOrchestrator.terminal(command.code, undefined, result.error);
346
- }
347
- continue; // Move to next command
348
- } catch (e: any) {
349
- console.log(chalk.red(` ❌ MCP Execution Error: ${e.message}`));
350
- }
351
- }
352
-
353
- const shouldExecute = await askPermission(command);
354
-
355
- if (shouldExecute) {
356
- try {
357
- // Persistent CWD logic: Trace 'cd' commands
358
- const lines = command.code.split('\n');
359
- for (const line of lines) {
360
- const trimmed = line.trim();
361
- if (trimmed.startsWith('cd ')) {
362
- let targetDir = trimmed.substring(3).replace(/['"]/g, '').trim();
363
- // Resolve relative to activeCwd
364
- const potentialCwd = path.isAbsolute(targetDir) ? targetDir : path.resolve(activeCwd, targetDir);
365
-
366
- if (fs.existsSync(potentialCwd) && fs.statSync(potentialCwd).isDirectory()) {
367
- activeCwd = potentialCwd;
368
- } else {
369
- // Directory doesn't exist - skip silently instead of crashing
370
- console.log(chalk.yellow(` ⚠️ Directory not found: ${targetDir} — staying in ${activeCwd}`));
371
- continue;
372
- }
373
- }
374
- }
375
-
376
- TUI.drawLiveTerminalStart(command.code);
377
-
378
- const shellPath = process.env.SHELL || '/bin/bash';
379
- const child = spawn(shellPath, ['-c', command.code], {
380
- cwd: activeCwd,
381
- env: { ...process.env, FORCE_COLOR: 'true' }
382
- });
383
-
384
- let stdout = '';
385
- let stderr = '';
386
- let isAborted = false;
387
- const commandStartTime = Date.now();
388
-
389
- const onData = (data: Buffer) => {
390
- if (Date.now() - commandStartTime < 500) return; // Hardened 500ms grace period
391
- if (data[0] === 13 || data[0] === 10 || data[0] === 27 || data[0] === 3) {
392
- isAborted = true;
393
- child.kill('SIGINT');
394
- }
395
- };
396
-
397
- const isRaw = process.stdin.isRaw;
398
- process.stdin.resume();
399
- if (process.stdin.setRawMode) process.stdin.setRawMode(true);
400
- process.stdin.on('data', onData);
401
-
402
- child.stdout.on('data', (data: Buffer) => {
403
- const chunk = data.toString();
404
- stdout += chunk;
405
- TUI.drawLiveTerminalLine(chunk);
406
- });
407
-
408
- child.stderr.on('data', (data: Buffer) => {
409
- const chunk = data.toString();
410
- stderr += chunk;
411
- TUI.drawLiveTerminalLine(chunk, true);
412
- });
413
-
414
- const { code } = await new Promise<{ code: number | null }>(resolve => {
415
- child.on('close', (code) => {
416
- process.stdin.removeListener('data', onData);
417
- if (process.stdin.setRawMode) process.stdin.setRawMode(isRaw);
418
- process.stdin.pause();
419
- TUI.drawLiveTerminalEnd();
420
- resolve({ code });
421
- });
422
- });
423
-
424
- accumulatedOutput += stdout;
425
- accumulatedError += stderr;
426
-
427
- if (isAborted) {
428
- console.log(chalk.yellow(' [🛑] Command aborted by brother.\n'));
429
- AgentOrchestrator.terminal(command.code, stdout, stderr + '\n(Aborted)');
430
- } else if (code === 0) {
431
- if (stdout || stderr) {
432
- AgentOrchestrator.terminal(command.code, stdout, stderr);
433
- } else {
434
- AgentOrchestrator.terminal(command.code, '✓ Success (No output)');
435
- }
436
- } else {
437
- AgentOrchestrator.terminal(command.code, stdout, stderr || `Failed with code ${code}`);
438
- }
439
- } catch (error: any) {
440
- accumulatedError += error.message;
441
- AgentOrchestrator.terminal(command.code, undefined, error.message);
442
- return { success: false, executed: true, error: accumulatedError, output: accumulatedOutput, newCwd: activeCwd };
443
- }
444
- }
445
- }
446
-
447
- // 3. Handle Surgical Patches
448
- for (const surgicalPatch of patches) {
449
- // Ensure patch happens relative to activeCwd if path is relative
450
- const originalPath = surgicalPatch.filePath;
451
- if (!path.isAbsolute(surgicalPatch.filePath)) {
452
- surgicalPatch.filePath = path.join(activeCwd, surgicalPatch.filePath);
453
- }
454
-
455
- let showFull = false;
456
- let decided = false;
457
-
458
- while (!decided) {
459
- const isTruncated = Patcher.showDiff(surgicalPatch, showFull);
460
-
461
- const choices = [
462
- { name: '✅ Apply this surgical patch brother', value: 'apply' },
463
- { name: '⏭️ Skip', value: 'skip' }
464
- ];
465
-
466
- if (isTruncated && !showFull) {
467
- choices.splice(1, 0, { name: '🔍 View Full Diff', value: 'full' });
468
- }
469
-
470
- const { action } = await inquirer.prompt([{
471
- type: 'list',
472
- name: 'action',
473
- message: chalk.cyan(`Patch ${originalPath}?`),
474
- choices: choices
475
- }]);
476
-
477
- if (action === 'full') {
478
- showFull = true;
479
- continue;
480
- }
481
-
482
- if (action === 'apply') {
483
- const result = Patcher.applyPatch(surgicalPatch);
484
- if (result.success) {
485
- console.log(chalk.green(` ✅ Applied surgical patch to ${originalPath}\n`));
486
- } else {
487
- console.log(chalk.red(` ❌ Error applying patch: ${result.error}\n`));
488
- return { success: false, executed: true, error: result.error, newCwd: activeCwd };
489
- }
490
- } else {
491
- console.log(chalk.gray(' Skipped.\n'));
492
- }
493
- decided = true;
494
- }
495
- }
496
-
497
- return {
498
- success: true,
499
- executed: true,
500
- output: accumulatedOutput.trim(),
501
- error: accumulatedError.trim(),
502
- newCwd: activeCwd
503
- };
504
- }
505
-
506
- /**
507
- * Command history for undo/rollback
508
- */
509
- export class CommandHistory {
510
- private history: Array<{ command: string; timestamp: Date; success: boolean }> = [];
511
-
512
- add(command: string, success: boolean) {
513
- this.history.push({
514
- command,
515
- timestamp: new Date(),
516
- success
517
- });
518
- }
519
-
520
- getHistory() {
521
- return this.history;
522
- }
523
-
524
- clear() {
525
- this.history = [];
526
- }
527
- }
528
-
529
- export const commandHistory = new CommandHistory();