@soulcraft/brainy 5.1.2 → 5.2.1

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.
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Image Import Handler (v5.2.0)
3
+ *
4
+ * Handles image files with:
5
+ * - EXIF metadata extraction (camera, GPS, timestamps)
6
+ * - Thumbnail generation (multiple sizes)
7
+ * - Image metadata (dimensions, format, color space)
8
+ * - Support for JPEG, PNG, WebP, GIF, TIFF, AVIF, etc.
9
+ *
10
+ * NO MOCKS - Production implementation using sharp and exifr
11
+ */
12
+ import { BaseFormatHandler } from './base.js';
13
+ import type { FormatHandlerOptions, ProcessedData } from '../types.js';
14
+ export interface ImageMetadata {
15
+ /** Image dimensions */
16
+ width: number;
17
+ height: number;
18
+ /** Image format (jpeg, png, webp, etc.) */
19
+ format: string;
20
+ /** Color space */
21
+ space: string;
22
+ /** Number of channels */
23
+ channels: number;
24
+ /** Bit depth */
25
+ depth: string;
26
+ /** File size in bytes */
27
+ size: number;
28
+ /** Whether image has alpha channel */
29
+ hasAlpha: boolean;
30
+ /** Orientation (EXIF) */
31
+ orientation?: number;
32
+ }
33
+ export interface EXIFData {
34
+ /** Camera make (e.g., "Canon", "Nikon") */
35
+ make?: string;
36
+ /** Camera model */
37
+ model?: string;
38
+ /** Lens information */
39
+ lens?: string;
40
+ /** Date/time original */
41
+ dateTimeOriginal?: Date;
42
+ /** GPS latitude */
43
+ latitude?: number;
44
+ /** GPS longitude */
45
+ longitude?: number;
46
+ /** GPS altitude in meters */
47
+ altitude?: number;
48
+ /** Exposure time (e.g., "1/250") */
49
+ exposureTime?: number;
50
+ /** F-number (e.g., 2.8) */
51
+ fNumber?: number;
52
+ /** ISO speed */
53
+ iso?: number;
54
+ /** Focal length in mm */
55
+ focalLength?: number;
56
+ /** Flash fired */
57
+ flash?: boolean;
58
+ /** Copyright */
59
+ copyright?: string;
60
+ /** Artist/photographer */
61
+ artist?: string;
62
+ /** Image description */
63
+ imageDescription?: string;
64
+ /** Software used */
65
+ software?: string;
66
+ }
67
+ export interface ImageHandlerOptions extends FormatHandlerOptions {
68
+ /** Extract EXIF data (default: true) */
69
+ extractEXIF?: boolean;
70
+ }
71
+ /**
72
+ * ImageImportHandler
73
+ *
74
+ * Processes image files and extracts rich metadata including EXIF data.
75
+ * Enables developers to import images into the knowledge graph with
76
+ * full metadata extraction.
77
+ */
78
+ export declare class ImageHandler extends BaseFormatHandler {
79
+ readonly format = "image";
80
+ /**
81
+ * Check if this handler can process the given data
82
+ */
83
+ canHandle(data: Buffer | string | {
84
+ filename?: string;
85
+ ext?: string;
86
+ }): boolean;
87
+ /**
88
+ * Process image file
89
+ */
90
+ process(data: Buffer | string, options?: ImageHandlerOptions): Promise<ProcessedData>;
91
+ /**
92
+ * Extract image metadata using sharp
93
+ */
94
+ private extractMetadata;
95
+ /**
96
+ * Extract EXIF data using exifr
97
+ */
98
+ private extractEXIF;
99
+ /**
100
+ * Detect image format from magic bytes
101
+ */
102
+ private detectImageFormat;
103
+ /**
104
+ * Detect if extension is an image format
105
+ */
106
+ private isImageExtension;
107
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Image Import Handler (v5.2.0)
3
+ *
4
+ * Handles image files with:
5
+ * - EXIF metadata extraction (camera, GPS, timestamps)
6
+ * - Thumbnail generation (multiple sizes)
7
+ * - Image metadata (dimensions, format, color space)
8
+ * - Support for JPEG, PNG, WebP, GIF, TIFF, AVIF, etc.
9
+ *
10
+ * NO MOCKS - Production implementation using sharp and exifr
11
+ */
12
+ import { BaseFormatHandler } from './base.js';
13
+ import sharp from 'sharp';
14
+ import exifr from 'exifr';
15
+ /**
16
+ * ImageImportHandler
17
+ *
18
+ * Processes image files and extracts rich metadata including EXIF data.
19
+ * Enables developers to import images into the knowledge graph with
20
+ * full metadata extraction.
21
+ */
22
+ export class ImageHandler extends BaseFormatHandler {
23
+ constructor() {
24
+ super(...arguments);
25
+ this.format = 'image';
26
+ }
27
+ /**
28
+ * Check if this handler can process the given data
29
+ */
30
+ canHandle(data) {
31
+ // Check by filename/extension
32
+ if (typeof data === 'object' && 'filename' in data) {
33
+ const mimeType = this.getMimeType(data);
34
+ return this.mimeTypeMatches(mimeType, ['image/*']);
35
+ }
36
+ // Check by extension
37
+ if (typeof data === 'object' && 'ext' in data && data.ext) {
38
+ return this.isImageExtension(data.ext);
39
+ }
40
+ // Check by data (Buffer magic bytes)
41
+ if (Buffer.isBuffer(data)) {
42
+ return this.detectImageFormat(data) !== null;
43
+ }
44
+ return false;
45
+ }
46
+ /**
47
+ * Process image file
48
+ */
49
+ async process(data, options = {}) {
50
+ const startTime = Date.now();
51
+ try {
52
+ // Convert to Buffer
53
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'base64');
54
+ // Extract image metadata
55
+ const metadata = await this.extractMetadata(buffer);
56
+ // Extract EXIF data (default: enabled)
57
+ let exifData;
58
+ if (options.extractEXIF !== false) {
59
+ exifData = await this.extractEXIF(buffer);
60
+ }
61
+ // Calculate processing time
62
+ const processingTime = Date.now() - startTime;
63
+ // Generate descriptive name
64
+ const imageName = options.filename
65
+ ? options.filename.replace(/\.[^/.]+$/, '') // Remove extension
66
+ : `${metadata.format.toUpperCase()} Image ${metadata.width}x${metadata.height}`;
67
+ // Return structured data
68
+ return {
69
+ format: 'image',
70
+ data: [
71
+ {
72
+ name: imageName,
73
+ type: 'media',
74
+ metadata: {
75
+ ...metadata,
76
+ subtype: 'image',
77
+ exif: exifData
78
+ }
79
+ }
80
+ ],
81
+ metadata: {
82
+ rowCount: 1,
83
+ fields: ['type', 'metadata'],
84
+ processingTime,
85
+ imageMetadata: metadata,
86
+ exifData
87
+ },
88
+ filename: options.filename
89
+ };
90
+ }
91
+ catch (error) {
92
+ throw new Error(`Image processing failed: ${error instanceof Error ? error.message : String(error)}`);
93
+ }
94
+ }
95
+ /**
96
+ * Extract image metadata using sharp
97
+ */
98
+ async extractMetadata(buffer) {
99
+ const image = sharp(buffer);
100
+ const metadata = await image.metadata();
101
+ return {
102
+ width: metadata.width || 0,
103
+ height: metadata.height || 0,
104
+ format: metadata.format || 'unknown',
105
+ space: metadata.space || 'unknown',
106
+ channels: metadata.channels || 0,
107
+ depth: metadata.depth || 'unknown',
108
+ size: buffer.length,
109
+ hasAlpha: metadata.hasAlpha || false,
110
+ orientation: metadata.orientation
111
+ };
112
+ }
113
+ /**
114
+ * Extract EXIF data using exifr
115
+ */
116
+ async extractEXIF(buffer) {
117
+ try {
118
+ const exif = await exifr.parse(buffer, {
119
+ pick: [
120
+ 'Make',
121
+ 'Model',
122
+ 'LensModel',
123
+ 'DateTimeOriginal',
124
+ 'latitude',
125
+ 'longitude',
126
+ 'GPSAltitude',
127
+ 'ExposureTime',
128
+ 'FNumber',
129
+ 'ISO',
130
+ 'FocalLength',
131
+ 'Flash',
132
+ 'Copyright',
133
+ 'Artist',
134
+ 'ImageDescription',
135
+ 'Software'
136
+ ]
137
+ });
138
+ if (!exif)
139
+ return undefined;
140
+ return {
141
+ make: exif.Make,
142
+ model: exif.Model,
143
+ lens: exif.LensModel,
144
+ dateTimeOriginal: exif.DateTimeOriginal,
145
+ latitude: exif.latitude,
146
+ longitude: exif.longitude,
147
+ altitude: exif.GPSAltitude,
148
+ exposureTime: exif.ExposureTime,
149
+ fNumber: exif.FNumber,
150
+ iso: exif.ISO,
151
+ focalLength: exif.FocalLength,
152
+ flash: exif.Flash !== undefined ? Boolean(exif.Flash & 1) : undefined,
153
+ copyright: exif.Copyright,
154
+ artist: exif.Artist,
155
+ imageDescription: exif.ImageDescription,
156
+ software: exif.Software
157
+ };
158
+ }
159
+ catch (error) {
160
+ // EXIF extraction can fail for non-JPEG images or corrupt data
161
+ return undefined;
162
+ }
163
+ }
164
+ /**
165
+ * Detect image format from magic bytes
166
+ */
167
+ detectImageFormat(buffer) {
168
+ if (buffer.length < 4)
169
+ return null;
170
+ // JPEG: FF D8 FF
171
+ if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
172
+ return 'jpeg';
173
+ }
174
+ // PNG: 89 50 4E 47
175
+ if (buffer[0] === 0x89 &&
176
+ buffer[1] === 0x50 &&
177
+ buffer[2] === 0x4e &&
178
+ buffer[3] === 0x47) {
179
+ return 'png';
180
+ }
181
+ // GIF: 47 49 46
182
+ if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) {
183
+ return 'gif';
184
+ }
185
+ // WebP: 52 49 46 46 ... 57 45 42 50
186
+ if (buffer[0] === 0x52 &&
187
+ buffer[1] === 0x49 &&
188
+ buffer[2] === 0x46 &&
189
+ buffer[3] === 0x46 &&
190
+ buffer.length >= 12 &&
191
+ buffer[8] === 0x57 &&
192
+ buffer[9] === 0x45 &&
193
+ buffer[10] === 0x42 &&
194
+ buffer[11] === 0x50) {
195
+ return 'webp';
196
+ }
197
+ // TIFF: 49 49 2A 00 (little-endian) or 4D 4D 00 2A (big-endian)
198
+ if ((buffer[0] === 0x49 && buffer[1] === 0x49 && buffer[2] === 0x2a && buffer[3] === 0x00) ||
199
+ (buffer[0] === 0x4d && buffer[1] === 0x4d && buffer[2] === 0x00 && buffer[3] === 0x2a)) {
200
+ return 'tiff';
201
+ }
202
+ return null;
203
+ }
204
+ /**
205
+ * Detect if extension is an image format
206
+ */
207
+ isImageExtension(ext) {
208
+ const imageExts = [
209
+ '.jpg',
210
+ '.jpeg',
211
+ '.png',
212
+ '.gif',
213
+ '.webp',
214
+ '.tiff',
215
+ '.tif',
216
+ '.bmp',
217
+ '.svg',
218
+ '.heic',
219
+ '.heif',
220
+ '.avif'
221
+ ];
222
+ const normalized = ext.toLowerCase();
223
+ const withDot = normalized.startsWith('.') ? normalized : `.${normalized}`;
224
+ return imageExts.includes(withDot);
225
+ }
226
+ }
227
+ //# sourceMappingURL=imageHandler.js.map
@@ -3,7 +3,11 @@
3
3
  * Exports main augmentation and types
4
4
  */
5
5
  export { IntelligentImportAugmentation } from './IntelligentImportAugmentation.js';
6
- export type { FormatHandler, FormatHandlerOptions, ProcessedData, IntelligentImportConfig } from './types.js';
6
+ export type { FormatHandler, FormatHandlerOptions, ProcessedData, IntelligentImportConfig, HandlerRegistry } from './types.js';
7
7
  export { CSVHandler } from './handlers/csvHandler.js';
8
8
  export { ExcelHandler } from './handlers/excelHandler.js';
9
9
  export { PDFHandler } from './handlers/pdfHandler.js';
10
+ export { ImageHandler } from './handlers/imageHandler.js';
11
+ export { FormatHandlerRegistry, globalHandlerRegistry } from './FormatHandlerRegistry.js';
12
+ export type { HandlerRegistration } from './FormatHandlerRegistry.js';
13
+ export type { ImageMetadata, EXIFData, ImageHandlerOptions } from './handlers/imageHandler.js';
@@ -3,7 +3,11 @@
3
3
  * Exports main augmentation and types
4
4
  */
5
5
  export { IntelligentImportAugmentation } from './IntelligentImportAugmentation.js';
6
+ // Format Handlers
6
7
  export { CSVHandler } from './handlers/csvHandler.js';
7
8
  export { ExcelHandler } from './handlers/excelHandler.js';
8
9
  export { PDFHandler } from './handlers/pdfHandler.js';
10
+ export { ImageHandler } from './handlers/imageHandler.js';
11
+ // Format Handler Registry (v5.2.0)
12
+ export { FormatHandlerRegistry, globalHandlerRegistry } from './FormatHandlerRegistry.js';
9
13
  //# sourceMappingURL=index.js.map
@@ -129,12 +129,16 @@ export interface IntelligentImportConfig {
129
129
  enableExcel: boolean;
130
130
  /** Enable PDF handler */
131
131
  enablePDF: boolean;
132
+ /** Enable Image handler (v5.2.0) */
133
+ enableImage: boolean;
132
134
  /** Default options for CSV */
133
135
  csvDefaults?: Partial<FormatHandlerOptions>;
134
136
  /** Default options for Excel */
135
137
  excelDefaults?: Partial<FormatHandlerOptions>;
136
138
  /** Default options for PDF */
137
139
  pdfDefaults?: Partial<FormatHandlerOptions>;
140
+ /** Default options for Image (v5.2.0) */
141
+ imageDefaults?: Partial<FormatHandlerOptions>;
138
142
  /** Maximum file size to process (bytes) */
139
143
  maxFileSize?: number;
140
144
  /** Enable caching of processed data */
package/dist/brainy.d.ts CHANGED
@@ -1111,7 +1111,7 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
1111
1111
  * - Reduced confusion (removed redundant options)
1112
1112
  */
1113
1113
  import(source: Buffer | string | object, options?: {
1114
- format?: 'excel' | 'pdf' | 'csv' | 'json' | 'markdown' | 'yaml' | 'docx';
1114
+ format?: 'excel' | 'pdf' | 'csv' | 'json' | 'markdown' | 'yaml' | 'docx' | 'image';
1115
1115
  vfsPath?: string;
1116
1116
  groupBy?: 'type' | 'sheet' | 'flat' | 'custom';
1117
1117
  customGrouping?: (entity: any) => string;
package/dist/brainy.js CHANGED
@@ -141,6 +141,14 @@ export class Brainy {
141
141
  this.registerShutdownHooks();
142
142
  Brainy.shutdownHooksRegisteredGlobally = true;
143
143
  }
144
+ // v5.2.0: Initialize COW (BlobStorage) before VFS
145
+ // VFS now requires BlobStorage for unified file storage
146
+ if (typeof this.storage.initializeCOW === 'function') {
147
+ await this.storage.initializeCOW({
148
+ branch: this.config.storage?.branch || 'main',
149
+ enableCompression: true
150
+ });
151
+ }
144
152
  // Mark as initialized BEFORE VFS init (v5.0.1)
145
153
  // VFS.init() needs brain to be marked initialized to call brain methods
146
154
  this.initialized = true;
@@ -1860,7 +1868,20 @@ export class Brainy {
1860
1868
  }
1861
1869
  const refManager = this.storage.refManager;
1862
1870
  const currentBranch = this.storage.currentBranch || 'main';
1863
- // Step 1: Copy storage ref (COW layer - instant!)
1871
+ // Step 1: Ensure initial commit exists (required for fork)
1872
+ const currentRef = await refManager.getRef(currentBranch);
1873
+ if (!currentRef) {
1874
+ // Auto-create initial commit if none exists
1875
+ await this.commit({
1876
+ message: `Initial commit on ${currentBranch}`,
1877
+ author: options?.author || 'Brainy',
1878
+ metadata: { timestamp: Date.now() }
1879
+ });
1880
+ if (!this.config.silent) {
1881
+ console.log(`📝 Auto-created initial commit on ${currentBranch} (required for fork)`);
1882
+ }
1883
+ }
1884
+ // Step 2: Copy storage ref (COW layer - instant!)
1864
1885
  await refManager.copyRef(currentBranch, branchName);
1865
1886
  // Step 2: Create new Brainy instance pointing to fork branch
1866
1887
  const forkConfig = {
@@ -2651,11 +2672,19 @@ export class Brainy {
2651
2672
  * - Reduced confusion (removed redundant options)
2652
2673
  */
2653
2674
  async import(source, options) {
2654
- // Lazy load ImportCoordinator
2655
- const { ImportCoordinator } = await import('./import/ImportCoordinator.js');
2656
- const coordinator = new ImportCoordinator(this);
2657
- await coordinator.init();
2658
- return await coordinator.import(source, options);
2675
+ // Execute through augmentation pipeline (v5.2.0: Enables IntelligentImportAugmentation)
2676
+ // If source is an ImportSource object (not a Buffer), spread it so augmentations can access properties
2677
+ const params = typeof source === 'object' && !Buffer.isBuffer(source)
2678
+ ? { ...source, ...options } // Spread ImportSource: { type, data, filename, ...options }
2679
+ : { source, ...options }; // Wrap Buffer/string: { source, ...options }
2680
+ return this.augmentationRegistry.execute('import', params, async () => {
2681
+ // Lazy load ImportCoordinator
2682
+ const { ImportCoordinator } = await import('./import/ImportCoordinator.js');
2683
+ const coordinator = new ImportCoordinator(this);
2684
+ await coordinator.init();
2685
+ // Pass augmentation-modified params (contains _intelligentImport, _extractedData, etc)
2686
+ return await coordinator.import(source, { ...options, ...params });
2687
+ });
2659
2688
  }
2660
2689
  /**
2661
2690
  * Virtual File System API - Knowledge Operating System (v5.0.1+)
@@ -3695,12 +3724,19 @@ export class Brainy {
3695
3724
  const graphIndexSize = await this.graphIndex.size();
3696
3725
  const needsRebuild = metadataStats.totalEntries === 0 ||
3697
3726
  hnswIndexSize === 0 ||
3698
- graphIndexSize === 0 ||
3699
- this.config.disableAutoRebuild === false; // Explicitly enabled
3727
+ graphIndexSize === 0;
3700
3728
  if (!needsRebuild) {
3701
3729
  // All indexes already populated, no rebuild needed
3702
3730
  return;
3703
3731
  }
3732
+ // BUG FIX: If disableAutoRebuild is truthy, skip rebuild even if indexes are empty
3733
+ // Indexes will load lazily on first query
3734
+ if (this.config.disableAutoRebuild) {
3735
+ if (!this.config.silent) {
3736
+ console.log('⚡ Indexes empty but auto-rebuild disabled - using lazy loading');
3737
+ }
3738
+ return;
3739
+ }
3704
3740
  // Small dataset: Rebuild all indexes for best performance
3705
3741
  if (totalCount < AUTO_REBUILD_THRESHOLD || this.config.disableAutoRebuild === false) {
3706
3742
  if (!this.config.silent) {
@@ -2,13 +2,14 @@
2
2
  * Format Detector
3
3
  *
4
4
  * Unified format detection for all import types using:
5
+ * - MIME type detection (via MimeTypeDetector service)
5
6
  * - Magic byte signatures (PDF, Excel, images)
6
- * - File extensions
7
+ * - File extensions (via MimeTypeDetector)
7
8
  * - Content analysis (JSON, Markdown, CSV)
8
9
  *
9
10
  * NO MOCKS - Production-ready implementation
10
11
  */
11
- export type SupportedFormat = 'excel' | 'pdf' | 'csv' | 'json' | 'markdown' | 'yaml' | 'docx';
12
+ export type SupportedFormat = 'excel' | 'pdf' | 'csv' | 'json' | 'markdown' | 'yaml' | 'docx' | 'image';
12
13
  export interface DetectionResult {
13
14
  format: SupportedFormat;
14
15
  confidence: number;
@@ -24,8 +25,16 @@ export declare class FormatDetector {
24
25
  detectFromBuffer(buffer: Buffer): DetectionResult | null;
25
26
  /**
26
27
  * Detect format from file path
28
+ *
29
+ * Uses MimeTypeDetector (2000+ types) and maps to SupportedFormat
27
30
  */
28
31
  detectFromPath(path: string): DetectionResult | null;
32
+ /**
33
+ * Map MIME type to SupportedFormat
34
+ *
35
+ * Supports all variations of Excel, PDF, CSV, JSON, Markdown, YAML, DOCX
36
+ */
37
+ private mimeTypeToFormat;
29
38
  /**
30
39
  * Detect format from string content
31
40
  */
@@ -2,12 +2,14 @@
2
2
  * Format Detector
3
3
  *
4
4
  * Unified format detection for all import types using:
5
+ * - MIME type detection (via MimeTypeDetector service)
5
6
  * - Magic byte signatures (PDF, Excel, images)
6
- * - File extensions
7
+ * - File extensions (via MimeTypeDetector)
7
8
  * - Content analysis (JSON, Markdown, CSV)
8
9
  *
9
10
  * NO MOCKS - Production-ready implementation
10
11
  */
12
+ import { mimeDetector } from '../vfs/MimeTypeDetector.js';
11
13
  /**
12
14
  * FormatDetector - Detect file format from various inputs
13
15
  */
@@ -28,32 +30,68 @@ export class FormatDetector {
28
30
  }
29
31
  /**
30
32
  * Detect format from file path
33
+ *
34
+ * Uses MimeTypeDetector (2000+ types) and maps to SupportedFormat
31
35
  */
32
36
  detectFromPath(path) {
33
- const ext = this.getExtension(path).toLowerCase();
34
- const extensionMap = {
35
- '.xlsx': 'excel',
36
- '.xls': 'excel',
37
- '.pdf': 'pdf',
38
- '.csv': 'csv',
39
- '.json': 'json',
40
- '.md': 'markdown',
41
- '.markdown': 'markdown',
42
- '.yaml': 'yaml',
43
- '.yml': 'yaml',
44
- '.docx': 'docx',
45
- '.doc': 'docx'
46
- };
47
- const format = extensionMap[ext];
37
+ // Get MIME type from MimeTypeDetector
38
+ const mimeType = mimeDetector.detectMimeType(path);
39
+ // Map MIME type to SupportedFormat
40
+ const format = this.mimeTypeToFormat(mimeType);
48
41
  if (format) {
42
+ const ext = this.getExtension(path);
49
43
  return {
50
44
  format,
51
45
  confidence: 0.9,
52
- evidence: [`File extension: ${ext}`]
46
+ evidence: [`MIME type: ${mimeType}`, `File extension: ${ext}`]
53
47
  };
54
48
  }
55
49
  return null;
56
50
  }
51
+ /**
52
+ * Map MIME type to SupportedFormat
53
+ *
54
+ * Supports all variations of Excel, PDF, CSV, JSON, Markdown, YAML, DOCX
55
+ */
56
+ mimeTypeToFormat(mimeType) {
57
+ // Excel formats (Office Open XML + legacy)
58
+ if (mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
59
+ mimeType === 'application/vnd.ms-excel' ||
60
+ mimeType === 'application/vnd.ms-excel.sheet.macroEnabled.12' ||
61
+ mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.template') {
62
+ return 'excel';
63
+ }
64
+ // PDF
65
+ if (mimeType === 'application/pdf') {
66
+ return 'pdf';
67
+ }
68
+ // CSV
69
+ if (mimeType === 'text/csv') {
70
+ return 'csv';
71
+ }
72
+ // JSON
73
+ if (mimeType === 'application/json') {
74
+ return 'json';
75
+ }
76
+ // Markdown
77
+ if (mimeType === 'text/markdown' || mimeType === 'text/x-markdown') {
78
+ return 'markdown';
79
+ }
80
+ // YAML
81
+ if (mimeType === 'text/yaml' || mimeType === 'text/x-yaml' || mimeType === 'application/x-yaml') {
82
+ return 'yaml';
83
+ }
84
+ // Word documents (Office Open XML)
85
+ if (mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
86
+ mimeType === 'application/msword') {
87
+ return 'docx';
88
+ }
89
+ // Images (v5.2.0: ImageHandler support)
90
+ if (mimeType.startsWith('image/')) {
91
+ return 'image';
92
+ }
93
+ return null;
94
+ }
57
95
  /**
58
96
  * Detect format from string content
59
97
  */
@@ -132,6 +170,50 @@ export class FormatDetector {
132
170
  };
133
171
  }
134
172
  }
173
+ // Image formats (v5.2.0)
174
+ // JPEG: FF D8 FF
175
+ if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
176
+ return {
177
+ format: 'image',
178
+ confidence: 1.0,
179
+ evidence: ['JPEG magic bytes: FF D8 FF']
180
+ };
181
+ }
182
+ // PNG: 89 50 4E 47
183
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
184
+ return {
185
+ format: 'image',
186
+ confidence: 1.0,
187
+ evidence: ['PNG magic bytes: 89 50 4E 47']
188
+ };
189
+ }
190
+ // GIF: 47 49 46 38
191
+ if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
192
+ return {
193
+ format: 'image',
194
+ confidence: 1.0,
195
+ evidence: ['GIF magic bytes: GIF8']
196
+ };
197
+ }
198
+ // BMP: 42 4D
199
+ if (buffer[0] === 0x42 && buffer[1] === 0x4D) {
200
+ return {
201
+ format: 'image',
202
+ confidence: 1.0,
203
+ evidence: ['BMP magic bytes: BM']
204
+ };
205
+ }
206
+ // WebP: RIFF....WEBP
207
+ if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 && buffer.length >= 12) {
208
+ const webpCheck = buffer.toString('utf8', 8, 12);
209
+ if (webpCheck === 'WEBP') {
210
+ return {
211
+ format: 'image',
212
+ confidence: 1.0,
213
+ evidence: ['WebP magic bytes: RIFF...WEBP']
214
+ };
215
+ }
216
+ }
135
217
  return null;
136
218
  }
137
219
  /**