@mrxkun/mcfast-mcp 4.0.6 → 4.0.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "4.0.6",
3
+ "version": "4.0.11",
4
4
  "description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. Optimized for AI code assistants with 80-98% latency reduction. Phase 4: ML Intelligence Layer.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -41,6 +41,10 @@ import {
41
41
  import { safeEdit } from './utils/backup.js';
42
42
  import { formatError } from './utils/error-formatter.js';
43
43
  import { MemoryEngine } from './memory/index.js';
44
+ import { getAuditQueue } from './utils/audit-queue.js';
45
+ import { getIntelligenceCache } from './utils/intelligence-cache.js';
46
+ import { getContextPrefetcher } from './utils/context-prefetcher.js';
47
+ import { parallelMemorySearch } from './utils/parallel-search.js';
44
48
 
45
49
  const execAsync = promisify(exec);
46
50
 
@@ -108,6 +112,62 @@ async function searchMemoryContext(instruction, maxResults = 5) {
108
112
  }
109
113
  }
110
114
 
115
+ /**
116
+ * Auto-trigger intelligence tools after successful edit
117
+ * Runs async in background to not block user
118
+ */
119
+ async function autoTriggerIntelligence(filePath, instruction) {
120
+ try {
121
+ // Only trigger if intelligence is enabled
122
+ if (process.env.MCFAST_INTELLIGENCE_AUTO !== 'true') {
123
+ return;
124
+ }
125
+
126
+ const engine = await getMemoryEngine();
127
+ const cache = getIntelligenceCache();
128
+
129
+ // 1. Detect patterns (cached for 5 minutes)
130
+ const patternCacheKey = { file: filePath, type: 'patterns' };
131
+ let patterns = cache.get('patterns', patternCacheKey);
132
+
133
+ if (!patterns) {
134
+ patterns = await engine.detectPatterns();
135
+ if (patterns && patterns.length > 0) {
136
+ cache.set('patterns', patternCacheKey, patterns);
137
+ console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 Detected ${patterns.length} patterns`);
138
+
139
+ // Log high-confidence patterns
140
+ patterns.filter(p => p.confidence > 0.8).forEach(p => {
141
+ console.error(`${colors.yellow}[Pattern]${colors.reset} ${p.type}: ${p.message}`);
142
+ });
143
+ }
144
+ }
145
+
146
+ // 2. Get suggestions (cached for 5 minutes)
147
+ const suggestionCacheKey = { file: filePath, type: 'suggestions' };
148
+ let suggestions = cache.get('suggestions', suggestionCacheKey);
149
+
150
+ if (!suggestions) {
151
+ suggestions = await engine.getSuggestions({ currentFile: filePath });
152
+ if (suggestions && suggestions.length > 0) {
153
+ cache.set('suggestions', suggestionCacheKey, suggestions);
154
+ console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 ${suggestions.length} suggestions available`);
155
+
156
+ // Show top suggestions
157
+ suggestions.slice(0, 3).forEach(s => {
158
+ console.error(`${colors.yellow}[Suggestion]${colors.reset} ${s.type}: ${s.message}`);
159
+ });
160
+ }
161
+ }
162
+
163
+ } catch (error) {
164
+ // Silent fail - intelligence should not break the tool
165
+ if (VERBOSE) {
166
+ console.error(`${colors.yellow}[Intelligence]${colors.reset} Auto-trigger failed: ${error.message}`);
167
+ }
168
+ }
169
+ }
170
+
111
171
  /**
112
172
  * Log edit to memory after successful edit
113
173
  */
@@ -728,6 +788,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
728
788
  /**
729
789
  * Report audit log to Dashboard
730
790
  * Sends operation logs to mcfast Dashboard for monitoring
791
+ * Now uses AuditQueue for batching to reduce HTTP overhead
731
792
  */
732
793
  async function reportAudit(data) {
733
794
  try {
@@ -755,42 +816,33 @@ async function reportAudit(data) {
755
816
  latency_ms: data.latency_ms || 0,
756
817
  instruction: data.instruction || '',
757
818
  files_count: data.files_count || 0,
819
+ diff_size: data.diff_size || 0, // ✅ Thêm diff_size
758
820
  input_tokens: data.input_tokens || 0,
759
821
  output_tokens: data.output_tokens || 0,
760
822
  strategy: data.strategy || null,
761
823
  error: data.error || null,
824
+ error_message: data.error || null, // ✅ Thêm error_message cho server
825
+ result_summary: data.result_summary || null, // ✅ Thêm result_summary
762
826
  metadata: data.metadata || {}
763
827
  };
764
828
 
765
- // Send to dashboard API
766
- const DASHBOARD_API_URL = process.env.MCFAST_DASHBOARD_URL || 'https://mcfast.vercel.app/api/v1/logs/audit';
829
+ // Use AuditQueue for batching instead of immediate send
830
+ const queue = getAuditQueue({
831
+ token: TOKEN,
832
+ apiUrl: process.env.MCFAST_DASHBOARD_URL || 'https://mcfast.vercel.app/api/v1/logs/audit',
833
+ batchSize: 5,
834
+ flushInterval: 5000,
835
+ verbose: VERBOSE
836
+ });
767
837
 
768
- if (VERBOSE) {
769
- console.error(`${colors.dim}[Audit] Sending to: ${DASHBOARD_API_URL}${colors.reset}`);
770
- console.error(`${colors.dim}[Audit] Tool: ${auditData.tool}, Status: ${auditData.status}${colors.reset}`);
771
- }
838
+ queue.add(auditData);
772
839
 
773
- const response = await fetch(DASHBOARD_API_URL, {
774
- method: 'POST',
775
- headers: {
776
- 'Content-Type': 'application/json',
777
- 'Authorization': `Bearer ${TOKEN}`
778
- },
779
- body: JSON.stringify(auditData)
780
- });
781
-
782
- if (!response.ok) {
783
- const errorText = await response.text().catch(() => 'Unknown error');
784
- if (VERBOSE) {
785
- console.error(`${colors.yellow}[Audit] Failed - HTTP ${response.status}: ${errorText}${colors.reset}`);
786
- }
787
- } else {
788
- if (VERBOSE) {
789
- console.error(`${colors.green}[Audit] Logged successfully ✓${colors.reset}`);
790
- }
840
+ if (VERBOSE) {
841
+ const stats = queue.getStats();
842
+ console.error(`${colors.dim}[Audit] Queued for batching. Queue size: ${stats.queued}${colors.reset}`);
791
843
  }
792
844
 
793
- // Also log to local file if configured
845
+ // Also log to local file if configured (immediate)
794
846
  if (process.env.MCFAST_AUDIT_FILE) {
795
847
  const logLine = JSON.stringify(auditData) + '\n';
796
848
  await fs.appendFile(process.env.MCFAST_AUDIT_FILE, logLine).catch((err) => {
@@ -1631,6 +1683,12 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1631
1683
  durationMs: Date.now() - editStartTime
1632
1684
  });
1633
1685
 
1686
+ // Auto-trigger intelligence tools in background (non-blocking)
1687
+ if (!result.isError) {
1688
+ const firstFile = Object.keys(files)[0];
1689
+ autoTriggerIntelligence(firstFile, instruction).catch(() => {});
1690
+ }
1691
+
1634
1692
  return result;
1635
1693
  }
1636
1694
 
@@ -1864,7 +1922,7 @@ async function handleReadFileInternal({ path: filePath, start_line, end_line, ma
1864
1922
  } else if (startLine && currentLine >= startLine) {
1865
1923
  lines.push(line);
1866
1924
  if (max_lines && lines.length >= max_lines) break;
1867
- } else if (lines.length < (max_lines || 2000)) {
1925
+ } else if (max_lines && lines.length < max_lines) {
1868
1926
  lines.push(line);
1869
1927
  } else {
1870
1928
  break;
@@ -2155,7 +2213,7 @@ async function handleSearchCode({ query, files, regex = false, caseSensitive = f
2155
2213
  });
2156
2214
  return {
2157
2215
  content: [{ type: "text", text: `❌ Search error: ${error.message}` }],
2158
- isError: true
2216
+ isError: true,
2159
2217
  };
2160
2218
  }
2161
2219
  }
@@ -2169,7 +2227,7 @@ async function handleSearchCodeAI({ query, files, contextLines = 2 }) {
2169
2227
  if (!TOKEN) {
2170
2228
  return {
2171
2229
  content: [{ type: "text", text: "❌ Error: MCFAST_TOKEN is missing. Please set it in your MCP config." }],
2172
- isError: true
2230
+ isError: true,
2173
2231
  };
2174
2232
  }
2175
2233
  try {
@@ -2744,13 +2802,14 @@ async function handleMemorySearch({ query, type = 'all', maxResults = 6, minScor
2744
2802
  score: result.finalScore || result.score
2745
2803
  }));
2746
2804
  } else {
2747
- const searchResult = await engine.intelligentSearch(query, { limit: maxResults });
2748
- results = searchResult.results.map(result => ({
2805
+ // Use parallel search for 'all' type - faster!
2806
+ const parallelResult = await parallelMemorySearch(engine, query, maxResults);
2807
+ results = parallelResult.results.map(result => ({
2749
2808
  type: 'chunk',
2750
- content: result.code || result.content,
2751
- file: result.filePath || result.path,
2752
- score: result.finalScore || result.score,
2753
- method: searchResult.metadata?.method
2809
+ content: result.content || result.code,
2810
+ file: result.file || result.filePath || result.path,
2811
+ score: result._score || result.score || 0.5,
2812
+ method: result._source
2754
2813
  }));
2755
2814
  }
2756
2815
 
@@ -13,7 +13,6 @@ import { DailyLogs } from './utils/daily-logs.js';
13
13
  import { SyncEngine } from './utils/sync-engine.js';
14
14
  import { PatternDetector, SuggestionEngine, StrategySelector } from '../intelligence/index.js';
15
15
  import path from 'path';
16
- import os from 'os';
17
16
 
18
17
  export class MemoryEngine {
19
18
  constructor(options = {}) {
@@ -23,25 +22,19 @@ export class MemoryEngine {
23
22
  this.projectPath = null;
24
23
  this.isInitialized = false;
25
24
 
26
- // Memory path for logs
27
- this.memoryPath = options.memoryPath || path.join(os.homedir(), '.mcfast', 'memory');
25
+ // Memory path - now stored in project directory
26
+ this.memoryPath = options.memoryPath || null; // Will be set on initialize
28
27
 
29
28
  // Daily Logs - auto-generated markdown notes
30
- this.dailyLogs = new DailyLogs({
31
- memoryPath: this.memoryPath
32
- });
29
+ this.dailyLogs = null; // Will be initialized on initialize
33
30
 
34
31
  // Sync Engine - local ↔ cloud (optional, auto-detect from MCFAST_TOKEN)
35
32
  this.syncEngine = null;
36
- const hasToken = options.apiKey || process.env.MCFAST_TOKEN;
37
- if (hasToken && options.enableSync !== false) {
38
- this.syncEngine = new SyncEngine({
39
- memoryPath: this.memoryPath,
40
- apiKey: hasToken,
41
- baseUrl: options.baseUrl || process.env.MCFAST_DASHBOARD_URL || 'https://mcfast.vercel.app',
42
- syncInterval: options.syncInterval || 5 * 60 * 1000
43
- });
44
- }
33
+ this.syncEngineOptions = {
34
+ apiKey: options.apiKey || process.env.MCFAST_TOKEN,
35
+ baseUrl: options.baseUrl || process.env.MCFAST_DASHBOARD_URL || 'https://mcfast.vercel.app',
36
+ syncInterval: options.syncInterval || 5 * 60 * 1000
37
+ };
45
38
 
46
39
  // Sử dụng UltraEnhancedEmbedder cho 90% accuracy
47
40
  this.embedder = new UltraEnhancedEmbedder({
@@ -69,41 +62,38 @@ export class MemoryEngine {
69
62
  this.patternDetector = null;
70
63
  this.suggestionEngine = null;
71
64
  this.strategySelector = null;
72
-
73
- if (this.intelligenceEnabled) {
74
- this.patternDetector = new PatternDetector({ db: this.db });
75
- this.suggestionEngine = new SuggestionEngine({
76
- db: this.db,
77
- memoryEngine: this
78
- });
79
- this.strategySelector = new StrategySelector({ db: this.db });
80
- }
81
65
  }
82
66
 
83
67
  async initialize(projectPath) {
84
68
  if (this.isInitialized) return;
85
69
 
86
70
  this.projectPath = projectPath;
87
- await this.db.initialize();
88
71
 
89
- // Load smart router config from dashboard
90
- if (this.smartRoutingEnabled) {
91
- await this.smartRouter.loadConfig();
92
- console.log('[MemoryEngine] Smart router config loaded');
72
+ // Set memory path to project directory
73
+ if (!this.memoryPath) {
74
+ this.memoryPath = path.join(projectPath, '.mcfast', 'memory');
93
75
  }
94
76
 
95
- // Test dashboard connection
96
- const connectionStatus = await this.dashboardClient.testConnection();
97
- if (connectionStatus.connected) {
98
- console.log('[MemoryEngine] ✅ Connected to Dashboard');
99
- } else {
100
- console.log('[MemoryEngine] ⚠️ Dashboard connection failed, using offline mode');
77
+ // Update database path if not explicitly set
78
+ if (!this.db.dbPath) {
79
+ this.db.dbPath = path.join(this.memoryPath, 'index.db');
101
80
  }
102
81
 
82
+ await this.db.initialize();
83
+
84
+ // Initialize Daily Logs with project-specific path
85
+ this.dailyLogs = new DailyLogs({
86
+ memoryPath: this.memoryPath
87
+ });
88
+
103
89
  // Initialize Sync Engine if configured
104
- if (this.syncEngine) {
105
- await this.syncEngine.initialize();
106
- console.log('[MemoryEngine] ✅ Sync Engine initialized');
90
+ if (this.syncEngineOptions.apiKey && this.syncEngineOptions.enableSync !== false) {
91
+ this.syncEngine = new SyncEngine({
92
+ memoryPath: this.memoryPath,
93
+ apiKey: this.syncEngineOptions.apiKey,
94
+ baseUrl: this.syncEngineOptions.baseUrl,
95
+ syncInterval: this.syncEngineOptions.syncInterval
96
+ });
107
97
  }
108
98
 
109
99
  this.watcher = new FileWatcher(projectPath, this);
@@ -1,15 +1,19 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import path from 'path';
3
- import os from 'os';
4
3
  import fs from 'fs/promises';
5
4
 
6
5
  export class MemoryDatabase {
7
- constructor(dbPath = path.join(os.homedir(), '.mcfast', 'memory', 'index.db')) {
6
+ constructor(dbPath = null) {
8
7
  this.dbPath = dbPath;
9
8
  this.db = null;
10
9
  }
11
10
 
12
11
  async initialize() {
12
+ // If dbPath not set, create in current directory
13
+ if (!this.dbPath) {
14
+ this.dbPath = path.join(process.cwd(), '.mcfast', 'memory', 'index.db');
15
+ }
16
+
13
17
  await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
14
18
  this.db = new Database(this.dbPath);
15
19
  this.db.pragma('journal_mode = WAL');
@@ -5,15 +5,15 @@
5
5
 
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
- import os from 'os';
9
8
 
10
9
  export class DailyLogs {
11
10
  constructor(options = {}) {
12
- this.memoryPath = options.memoryPath || path.join(os.homedir(), '.mcfast', 'memory');
13
- this.dateFormat = options.dateFormat || 'yyyy-MM-dd';
14
- }
15
-
16
- ensureMemoryDir() {
11
+ this.memoryPath = options.memoryPath;
12
+ if (!this.memoryPath) {
13
+ throw new Error('memoryPath is required for DailyLogs');
14
+ }
15
+
16
+ // Ensure directory exists
17
17
  if (!fs.existsSync(this.memoryPath)) {
18
18
  fs.mkdirSync(this.memoryPath, { recursive: true });
19
19
  }
@@ -47,8 +47,6 @@ export class DailyLogs {
47
47
  * Log a file creation event
48
48
  */
49
49
  logFileCreated(filePath, facts = []) {
50
- this.ensureMemoryDir();
51
-
52
50
  const content = [
53
51
  `## ${this.formatTime()} - File Created`,
54
52
  `**File:** \`${filePath}\``,
@@ -63,8 +61,6 @@ export class DailyLogs {
63
61
  * Log a file modification event
64
62
  */
65
63
  logFileModified(filePath, changes = {}) {
66
- this.ensureMemoryDir();
67
-
68
64
  const parts = [`**File:** \`${filePath}\``];
69
65
 
70
66
  if (changes.added && changes.added.length > 0) {
@@ -92,8 +88,6 @@ export class DailyLogs {
92
88
  * Log a file deletion event
93
89
  */
94
90
  logFileDeleted(filePath) {
95
- this.ensureMemoryDir();
96
-
97
91
  const content = [
98
92
  `## ${this.formatTime()} - File Deleted`,
99
93
  `**File:** \`${filePath}\``,
@@ -107,8 +101,6 @@ export class DailyLogs {
107
101
  * Log an edit session
108
102
  */
109
103
  logEdit(edit) {
110
- this.ensureMemoryDir();
111
-
112
104
  const {
113
105
  instruction,
114
106
  files = [],
@@ -139,8 +131,6 @@ export class DailyLogs {
139
131
  * Log a search query
140
132
  */
141
133
  logSearch(query, resultsCount, duration) {
142
- this.ensureMemoryDir();
143
-
144
134
  const content = [
145
135
  `## ${this.formatTime()} - Search`,
146
136
  `**Query:** "${query}"`,
@@ -156,8 +146,6 @@ export class DailyLogs {
156
146
  * Add a curated memory entry to MEMORY.md
157
147
  */
158
148
  addCuratedMemory(title, content, tags = []) {
159
- this.ensureMemoryDir();
160
-
161
149
  const memoryPath = this.getMemoryFilePath();
162
150
  const timestamp = new Date().toISOString();
163
151
 
@@ -219,8 +207,6 @@ export class DailyLogs {
219
207
  * Get logs for a date range
220
208
  */
221
209
  getLogsInRange(startDate, endDate) {
222
- this.ensureMemoryDir();
223
-
224
210
  const logs = [];
225
211
  const files = fs.readdirSync(this.memoryPath)
226
212
  .filter(f => f.match(/^\d{4}-\d{2}-\d{2}\.md$/))
@@ -241,8 +227,6 @@ export class DailyLogs {
241
227
  * Clean up old logs (keep last N days)
242
228
  */
243
229
  cleanupOldLogs(keepDays = 30) {
244
- this.ensureMemoryDir();
245
-
246
230
  const cutoffDate = new Date();
247
231
  cutoffDate.setDate(cutoffDate.getDate() - keepDays);
248
232
  const cutoffStr = cutoffDate.toISOString().split('T')[0].replace(/-/g, '');
@@ -7,6 +7,7 @@ import { parse } from '../../strategies/tree-sitter/index.js';
7
7
  import { detectLanguage } from '../../strategies/syntax-validator.js';
8
8
  import { Chunker } from './chunker.js';
9
9
  import { Embedder } from './embedder.js';
10
+ import { getQuery } from '../../strategies/tree-sitter/languages.js';
10
11
  import crypto from 'crypto';
11
12
 
12
13
  export class CodeIndexer {
@@ -58,14 +59,14 @@ export class CodeIndexer {
58
59
  const fileId = this.generateFileId(filePath);
59
60
 
60
61
  try {
61
- const { QUERIES } = await import('../../strategies/tree-sitter/queries.js');
62
- const queries = QUERIES[language];
62
+ // Use getQuery to compile the query properly
63
+ const query = await getQuery(language, 'definitions');
63
64
 
64
- if (queries?.definitions) {
65
- const captures = queries.definitions.captures(ast.rootNode);
65
+ if (query) {
66
+ const captures = query.captures(ast.rootNode);
66
67
 
67
68
  for (const capture of captures) {
68
- if (capture.name === 'name') {
69
+ if (capture.name === 'name' || capture.name === 'function') {
69
70
  facts.push({
70
71
  id: this.generateFactId(fileId, capture.node.text, 'function'),
71
72
  file_id: fileId,
@@ -6,30 +6,19 @@
6
6
 
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
- import os from 'os';
10
9
  import crypto from 'crypto';
11
10
 
12
11
  export class SyncEngine {
13
12
  constructor(options = {}) {
14
- this.memoryPath = options.memoryPath || path.join(os.homedir(), '.mcfast', 'memory');
15
-
16
- // Use Dashboard URL for sync (same as DashboardClient)
17
- this.baseUrl = options.baseUrl || process.env.MCFAST_DASHBOARD_URL || 'https://mcfast.vercel.app';
18
- this.apiKey = options.apiKey || process.env.MCFAST_TOKEN;
13
+ this.memoryPath = options.memoryPath;
14
+ if (!this.memoryPath) {
15
+ throw new Error('memoryPath is required for SyncEngine');
16
+ }
19
17
 
18
+ this.apiKey = options.apiKey;
19
+ this.baseUrl = options.baseUrl || 'https://mcfast.vercel.app';
20
20
  this.syncInterval = options.syncInterval || 5 * 60 * 1000; // 5 minutes
21
- this.lastSyncTime = null;
22
- this.syncQueue = [];
23
- this.isOnline = true;
24
- this.isSyncing = false;
25
- this.listeners = new Set();
26
-
27
- // Check online status
28
- if (typeof window !== 'undefined') {
29
- this.isOnline = navigator.onLine;
30
- window.addEventListener('online', () => this.handleOnline());
31
- window.addEventListener('offline', () => this.handleOffline());
32
- }
21
+ this.syncTimer = null;
33
22
  }
34
23
 
35
24
  /**
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Audit Queue with Batching
3
+ * Buffers audit logs and sends them in batches to reduce HTTP overhead
4
+ */
5
+
6
+ import { colors } from './colors.js';
7
+
8
+ export class AuditQueue {
9
+ constructor(options = {}) {
10
+ this.queue = [];
11
+ this.batchSize = options.batchSize || 5;
12
+ this.flushInterval = options.flushInterval || 5000; // 5 seconds
13
+ this.apiUrl = options.apiUrl || 'https://mcfast.vercel.app/api/v1/logs/batch';
14
+ this.token = options.token || process.env.MCFAST_TOKEN;
15
+ this.verbose = options.verbose || false;
16
+
17
+ // Start flush timer
18
+ this.flushTimer = setInterval(() => this.flush(), this.flushInterval);
19
+
20
+ // Flush on process exit
21
+ process.on('beforeExit', () => this.flushSync());
22
+ process.on('SIGINT', () => this.flushSync());
23
+ process.on('SIGTERM', () => this.flushSync());
24
+ }
25
+
26
+ add(data) {
27
+ // Add timestamp if not present
28
+ const auditData = {
29
+ timestamp: new Date().toISOString(),
30
+ ...data
31
+ };
32
+
33
+ this.queue.push(auditData);
34
+
35
+ if (this.verbose) {
36
+ console.error(`${colors.dim}[AuditQueue] Added to queue. Size: ${this.queue.length}${colors.reset}`);
37
+ }
38
+
39
+ // Flush immediately if batch size reached
40
+ if (this.queue.length >= this.batchSize) {
41
+ this.flush();
42
+ }
43
+ }
44
+
45
+ async flush() {
46
+ if (this.queue.length === 0) return;
47
+ if (!this.token) return; // Skip if no token
48
+
49
+ // Take batch from queue
50
+ const batch = this.queue.splice(0, this.batchSize);
51
+
52
+ try {
53
+ if (this.verbose) {
54
+ console.error(`${colors.dim}[AuditQueue] Flushing ${batch.length} logs...${colors.reset}`);
55
+ }
56
+
57
+ // Send batch using the batch API
58
+ const response = await fetch(this.apiUrl, {
59
+ method: 'POST',
60
+ headers: {
61
+ 'Content-Type': 'application/json',
62
+ 'Authorization': `Bearer ${this.token}`
63
+ },
64
+ body: JSON.stringify({ logs: batch, stream: false })
65
+ });
66
+
67
+ if (!response.ok) {
68
+ // Put back in queue if failed (will retry next flush)
69
+ this.queue.unshift(...batch);
70
+ if (this.verbose) {
71
+ console.error(`${colors.yellow}[AuditQueue] Flush failed, will retry${colors.reset}`);
72
+ }
73
+ } else {
74
+ const result = await response.json();
75
+ if (this.verbose) {
76
+ console.error(`${colors.green}[AuditQueue] Flushed ${result.stored || batch.length} logs ✓${colors.reset}`);
77
+ }
78
+ }
79
+ } catch (error) {
80
+ // Put back in queue if failed
81
+ this.queue.unshift(...batch);
82
+ if (this.verbose) {
83
+ console.error(`${colors.yellow}[AuditQueue] Network error, will retry: ${error.message}${colors.reset}`);
84
+ }
85
+ }
86
+ }
87
+
88
+ flushSync() {
89
+ // Synchronous flush for process exit
90
+ if (this.queue.length === 0) return;
91
+
92
+ // In real implementation, you might want to use sync HTTP or write to file
93
+ if (this.verbose) {
94
+ console.error(`${colors.dim}[AuditQueue] ${this.queue.length} logs remaining on exit${colors.reset}`);
95
+ }
96
+ }
97
+
98
+ getStats() {
99
+ return {
100
+ queued: this.queue.length,
101
+ batchSize: this.batchSize,
102
+ flushInterval: this.flushInterval
103
+ };
104
+ }
105
+
106
+ destroy() {
107
+ clearInterval(this.flushTimer);
108
+ return this.flush();
109
+ }
110
+ }
111
+
112
+ // Singleton instance
113
+ let auditQueue = null;
114
+
115
+ export function getAuditQueue(options = {}) {
116
+ if (!auditQueue) {
117
+ auditQueue = new AuditQueue(options);
118
+ }
119
+ return auditQueue;
120
+ }
121
+
122
+ export function resetAuditQueue() {
123
+ if (auditQueue) {
124
+ auditQueue.destroy();
125
+ auditQueue = null;
126
+ }
127
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Color utilities for terminal output
3
+ */
4
+
5
+ export const colors = {
6
+ reset: '\x1b[0m',
7
+ bright: '\x1b[1m',
8
+ dim: '\x1b[2m',
9
+ underscore: '\x1b[4m',
10
+ blink: '\x1b[5m',
11
+ reverse: '\x1b[7m',
12
+ hidden: '\x1b[8m',
13
+
14
+ black: '\x1b[30m',
15
+ red: '\x1b[31m',
16
+ green: '\x1b[32m',
17
+ yellow: '\x1b[33m',
18
+ blue: '\x1b[34m',
19
+ magenta: '\x1b[35m',
20
+ cyan: '\x1b[36m',
21
+ white: '\x1b[37m',
22
+
23
+ bgBlack: '\x1b[40m',
24
+ bgRed: '\x1b[41m',
25
+ bgGreen: '\x1b[42m',
26
+ bgYellow: '\x1b[43m',
27
+ bgBlue: '\x1b[44m',
28
+ bgMagenta: '\x1b[45m',
29
+ bgCyan: '\x1b[46m',
30
+ bgWhite: '\x1b[47m'
31
+ };