agentvibes 5.5.0 → 5.6.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.
Files changed (100) hide show
  1. package/.agentvibes/config.json +3 -30
  2. package/.claude/config/background-music-enabled.txt +1 -1
  3. package/.claude/config/background-music-position.txt +6 -6
  4. package/.claude/github-star-reminder.txt +1 -1
  5. package/.claude/hooks/play-tts-ssh-remote.sh +119 -42
  6. package/.claude/hooks/play-tts-windows-receiver.sh +31 -0
  7. package/.claude/hooks/stop.sh +2 -27
  8. package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
  9. package/.claude/hooks-windows/play-tts.ps1 +23 -7
  10. package/.claude/piper-voices-dir.txt +1 -1
  11. package/.clawdbot/skill/README.md +326 -0
  12. package/.mcp.json +17 -27
  13. package/README.md +73 -82
  14. package/RELEASE_NOTES.md +61 -0
  15. package/bin/agent-vibes +39 -39
  16. package/package.json +1 -1
  17. package/src/bmad-detector.js +71 -71
  18. package/src/cli/list-personalities.js +110 -110
  19. package/src/cli/list-voices.js +114 -114
  20. package/src/commands/bmad-voices.js +394 -394
  21. package/src/commands/install-mcp.js +476 -476
  22. package/src/console/brand-colors.js +13 -13
  23. package/src/console/constants/personalities.js +44 -44
  24. package/src/console/modals/modal-overlay.js +247 -247
  25. package/src/console/navigation.js +5 -1
  26. package/src/console/tabs/agents-tab.js +5 -5
  27. package/src/console/tabs/help-tab.js +314 -314
  28. package/src/console/tabs/readme-tab.js +272 -272
  29. package/src/console/tabs/setup-tab.js +32 -17
  30. package/src/console/tabs/voices-tab.js +2 -2
  31. package/src/console/widgets/destroy-list.js +25 -25
  32. package/src/console/widgets/notice.js +55 -55
  33. package/src/console/widgets/personality-picker.js +213 -213
  34. package/src/console/widgets/reverb-picker.js +97 -97
  35. package/src/console/widgets/track-picker.js +1 -1
  36. package/src/i18n/de.js +202 -202
  37. package/src/i18n/es.js +202 -202
  38. package/src/i18n/fr.js +202 -202
  39. package/src/i18n/hi.js +202 -202
  40. package/src/i18n/ja.js +202 -202
  41. package/src/i18n/ko.js +202 -202
  42. package/src/i18n/pt.js +202 -202
  43. package/src/i18n/strings.js +54 -54
  44. package/src/i18n/zh-CN.js +202 -202
  45. package/src/installer/language-screen.js +31 -31
  46. package/src/installer/music-file-input.js +304 -304
  47. package/src/services/agent-voice-store.js +420 -423
  48. package/src/services/config-service.js +264 -264
  49. package/src/services/language-service.js +47 -47
  50. package/src/services/llm-provider-service.js +11 -4
  51. package/src/services/navigation-service.js +34 -10
  52. package/src/services/provider-service.js +143 -143
  53. package/src/utils/audio-duration-validator.js +298 -298
  54. package/src/utils/audio-format-validator.js +277 -277
  55. package/src/utils/dependency-checker.js +469 -469
  56. package/src/utils/file-ownership-verifier.js +358 -358
  57. package/src/utils/list-formatter.js +194 -194
  58. package/src/utils/music-file-validator.js +285 -285
  59. package/src/utils/preview-list-prompt.js +136 -136
  60. package/src/utils/secure-music-storage.js +412 -412
  61. package/.agentvibes/LITE-MODE.md +0 -236
  62. package/.agentvibes/README.md +0 -136
  63. package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +0 -141
  64. package/.agentvibes/backups/agents/analyst_20260204_144958.md +0 -78
  65. package/.agentvibes/backups/agents/architect_20260204_144958.md +0 -72
  66. package/.agentvibes/backups/agents/dev_20260204_144958.md +0 -74
  67. package/.agentvibes/backups/agents/pm_20260204_144958.md +0 -72
  68. package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +0 -64
  69. package/.agentvibes/backups/agents/sm_20260204_144958.md +0 -87
  70. package/.agentvibes/backups/agents/tea_20260204_144958.md +0 -79
  71. package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +0 -82
  72. package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +0 -80
  73. package/.agentvibes/config/README-personality-defaults.md +0 -162
  74. package/.agentvibes/config/agentvibes.json +0 -1
  75. package/.agentvibes/config/mode.txt +0 -1
  76. package/.agentvibes/config/personality-voice-defaults.default.json +0 -21
  77. package/.agentvibes/config/save-audio.txt +0 -1
  78. package/.agentvibes/config/voice-metadata.json +0 -160
  79. package/.agentvibes/hooks/help.sh +0 -191
  80. package/.agentvibes/hooks/post-tool-use-lite.sh +0 -111
  81. package/.agentvibes/hooks/save-audio-manager.sh +0 -162
  82. package/.agentvibes/hooks/session-start-full-optimized.sh +0 -102
  83. package/.agentvibes/hooks/session-start-full.sh +0 -142
  84. package/.agentvibes/hooks/session-start-lite-v2.sh +0 -34
  85. package/.agentvibes/hooks/session-start-lite.sh +0 -29
  86. package/.agentvibes/hooks/stop-lite.sh +0 -115
  87. package/.agentvibes/hooks/switch-mode.sh +0 -215
  88. package/.agentvibes/output-styles/audio-summary.md +0 -30
  89. package/.claude/audio/voice-samples/piper/alan.wav +0 -0
  90. package/.claude/audio/voice-samples/piper/amy.wav +0 -0
  91. package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
  92. package/.claude/audio/voice-samples/piper/joe.wav +0 -0
  93. package/.claude/audio/voice-samples/piper/john.wav +0 -0
  94. package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
  95. package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
  96. package/.claude/audio/voice-samples/piper/linda.wav +0 -0
  97. package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
  98. package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
  99. package/.claude/hooks/post-response.sh +0 -41
  100. package/bin/ensure-soprano-running.sh +0 -43
@@ -1,277 +1,277 @@
1
- /**
2
- * Audio Format Validator - Magic Number Detection
3
- * Story 4.2: Audio Format Detection (Magic Number Validation)
4
- *
5
- * Validates audio file format by checking file headers (magic numbers).
6
- * Supported formats: MP3, WAV, OGG, M4A
7
- *
8
- * @module audio-format-validator
9
- * @requires fs
10
- */
11
-
12
- import fs from 'node:fs';
13
- import path from 'node:path';
14
-
15
- /**
16
- * Supported audio formats with their magic number signatures
17
- */
18
- const AUDIO_FORMATS = {
19
- mp3: {
20
- extension: '.mp3',
21
- magicNumbers: [
22
- // MPEG-1/2/2.5 Audio Frame Header
23
- { bytes: Buffer.from([0xFF, 0xFB]), offset: 0, name: 'MP3 MPEG-2 Layer III' },
24
- { bytes: Buffer.from([0xFF, 0xFA]), offset: 0, name: 'MP3 MPEG-1 Layer III' },
25
- // ID3 Tag (v2.x and v2.4)
26
- { bytes: Buffer.from([0x49, 0x44, 0x33]), offset: 0, name: 'ID3v2 Tag' } // "ID3"
27
- ]
28
- },
29
- wav: {
30
- extension: '.wav',
31
- magicNumbers: [
32
- { bytes: Buffer.from('RIFF'), offset: 0, name: 'RIFF Header' },
33
- { bytes: Buffer.from('WAVE'), offset: 8, name: 'WAV Format' }
34
- ]
35
- },
36
- ogg: {
37
- extension: '.ogg',
38
- magicNumbers: [
39
- { bytes: Buffer.from('OggS'), offset: 0, name: 'OggS Header' }
40
- ]
41
- },
42
- m4a: {
43
- extension: '.m4a',
44
- magicNumbers: [
45
- { bytes: Buffer.from('ftypisom'), offset: 4, name: 'M4A ISO Base Media' },
46
- { bytes: Buffer.from('ftypM4A'), offset: 4, name: 'M4A Apple iTunes' }
47
- ]
48
- }
49
- };
50
-
51
- const SUPPORTED_EXTENSIONS = Object.values(AUDIO_FORMATS).map(f => f.extension);
52
-
53
- /**
54
- * Story 4.2: Detect audio format by checking magic numbers in file header
55
- *
56
- * Reads the first 256 bytes of the file to check magic number signatures.
57
- * Does NOT require the file to have the correct extension matching format.
58
- *
59
- * @param {string} filePath - Path to audio file (must already be validated with isPathSafe)
60
- * @returns {Object} { isValid: boolean, error: string|null, format: string|null, detectedFormat: string|null }
61
- *
62
- * Object properties:
63
- * - isValid: true if file is valid audio format
64
- * - error: null on success, error message on failure
65
- * - format: detected format ('mp3', 'wav', 'ogg', 'm4a') or null
66
- * - detectedFormat: human-readable format name (e.g., "MP3 MPEG-1 Layer III")
67
- */
68
- function detectAudioFormat(filePath) {
69
- try {
70
- if (!filePath || typeof filePath !== 'string') {
71
- return {
72
- isValid: false,
73
- error: 'File path must be a non-empty string',
74
- format: null,
75
- detectedFormat: null
76
- };
77
- }
78
-
79
- // Check file exists and is readable
80
- if (!fs.existsSync(filePath)) {
81
- return {
82
- isValid: false,
83
- error: 'File does not exist',
84
- format: null,
85
- detectedFormat: null
86
- };
87
- }
88
-
89
- const stats = fs.statSync(filePath);
90
- if (!stats.isFile()) {
91
- return {
92
- isValid: false,
93
- error: 'Path must be a regular file',
94
- format: null,
95
- detectedFormat: null
96
- };
97
- }
98
-
99
- // Check minimum file size (at least 12 bytes for WAV format check)
100
- if (stats.size < 12) {
101
- return {
102
- isValid: false,
103
- error: 'File is too small to be a valid audio file (minimum 12 bytes)',
104
- format: null,
105
- detectedFormat: null
106
- };
107
- }
108
-
109
- // Read first 256 bytes for magic number detection
110
- const buffer = Buffer.alloc(256);
111
- const fd = fs.openSync(filePath, 'r');
112
- try {
113
- fs.readSync(fd, buffer, 0, 256, 0);
114
- } finally {
115
- fs.closeSync(fd);
116
- }
117
-
118
- // Check each format's magic numbers
119
- for (const [formatKey, formatInfo] of Object.entries(AUDIO_FORMATS)) {
120
- for (const magicInfo of formatInfo.magicNumbers) {
121
- // Check if magic bytes exist at the specified offset
122
- const isMatch = buffer.subarray(magicInfo.offset, magicInfo.offset + magicInfo.bytes.length)
123
- .equals(magicInfo.bytes);
124
-
125
- if (isMatch) {
126
- return {
127
- isValid: true,
128
- error: null,
129
- format: formatKey,
130
- detectedFormat: magicInfo.name
131
- };
132
- }
133
- }
134
- }
135
-
136
- // No magic numbers matched
137
- return {
138
- isValid: false,
139
- error: `Unsupported audio format. Supported formats: ${SUPPORTED_EXTENSIONS.join(', ')}`,
140
- format: null,
141
- detectedFormat: null
142
- };
143
-
144
- } catch (err) {
145
- return {
146
- isValid: false,
147
- error: `Error detecting audio format: ${err.message}`,
148
- format: null,
149
- detectedFormat: null
150
- };
151
- }
152
- }
153
-
154
- /**
155
- * Story 4.2: Validate that file extension matches detected audio format
156
- *
157
- * Ensures the file extension is correct for the detected format.
158
- * This prevents accidental misnamed files (e.g., .txt file with MP3 content).
159
- *
160
- * @param {string} filePath - Path to audio file
161
- * @param {string} detectedFormat - Format from detectAudioFormat() result
162
- * @returns {Object} { isValid: boolean, error: string|null }
163
- */
164
- function validateFileExtension(filePath, detectedFormat) {
165
- try {
166
- if (!filePath || !detectedFormat) {
167
- return {
168
- isValid: false,
169
- error: 'File path and detected format are required'
170
- };
171
- }
172
-
173
- const ext = path.extname(filePath).toLowerCase();
174
- const expectedExt = AUDIO_FORMATS[detectedFormat]?.extension;
175
-
176
- if (!expectedExt) {
177
- return {
178
- isValid: false,
179
- error: `Unknown detected format: ${detectedFormat}`
180
- };
181
- }
182
-
183
- if (ext === expectedExt) {
184
- return {
185
- isValid: true,
186
- error: null
187
- };
188
- }
189
-
190
- // Extension doesn't match - return specific error
191
- return {
192
- isValid: false,
193
- error: `File extension (${ext || 'none'}) doesn't match actual format (.${detectedFormat})`
194
- };
195
-
196
- } catch (err) {
197
- return {
198
- isValid: false,
199
- error: `Error validating file extension: ${err.message}`
200
- };
201
- }
202
- }
203
-
204
- /**
205
- * Story 4.2: Comprehensive audio file validation
206
- *
207
- * Combines format detection and extension validation in one call.
208
- * Returns false only if BOTH format is unsupported AND extension is wrong.
209
- * Returns true if format is valid (even if extension differs).
210
- *
211
- * @param {string} filePath - Path to audio file
212
- * @param {Object} options - Validation options
213
- * @param {boolean} options.strictExtension - If true, reject files with wrong extension (default: false)
214
- * @returns {Object} { isValid: boolean, error: string|null, format: string|null, detectedFormat: string|null, extensionMatch: boolean }
215
- */
216
- function validateAudioFile(filePath, options = {}) {
217
- const { strictExtension = false } = options;
218
-
219
- try {
220
- // Detect format first
221
- const formatResult = detectAudioFormat(filePath);
222
-
223
- if (!formatResult.isValid) {
224
- return {
225
- isValid: false,
226
- error: formatResult.error,
227
- format: null,
228
- detectedFormat: null,
229
- extensionMatch: false
230
- };
231
- }
232
-
233
- // Check extension if strict mode enabled
234
- if (strictExtension) {
235
- const extResult = validateFileExtension(filePath, formatResult.format);
236
- if (!extResult.isValid) {
237
- return {
238
- isValid: false,
239
- error: extResult.error,
240
- format: formatResult.format,
241
- detectedFormat: formatResult.detectedFormat,
242
- extensionMatch: false
243
- };
244
- }
245
- }
246
-
247
- // Format is valid - check if extension matches (informational)
248
- const ext = path.extname(filePath).toLowerCase();
249
- const expectedExt = AUDIO_FORMATS[formatResult.format]?.extension;
250
- const extensionMatch = ext === expectedExt;
251
-
252
- return {
253
- isValid: true,
254
- error: null,
255
- format: formatResult.format,
256
- detectedFormat: formatResult.detectedFormat,
257
- extensionMatch
258
- };
259
-
260
- } catch (err) {
261
- return {
262
- isValid: false,
263
- error: `Unexpected validation error: ${err.message}`,
264
- format: null,
265
- detectedFormat: null,
266
- extensionMatch: false
267
- };
268
- }
269
- }
270
-
271
- export {
272
- detectAudioFormat,
273
- validateFileExtension,
274
- validateAudioFile,
275
- AUDIO_FORMATS,
276
- SUPPORTED_EXTENSIONS
277
- };
1
+ /**
2
+ * Audio Format Validator - Magic Number Detection
3
+ * Story 4.2: Audio Format Detection (Magic Number Validation)
4
+ *
5
+ * Validates audio file format by checking file headers (magic numbers).
6
+ * Supported formats: MP3, WAV, OGG, M4A
7
+ *
8
+ * @module audio-format-validator
9
+ * @requires fs
10
+ */
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+
15
+ /**
16
+ * Supported audio formats with their magic number signatures
17
+ */
18
+ const AUDIO_FORMATS = {
19
+ mp3: {
20
+ extension: '.mp3',
21
+ magicNumbers: [
22
+ // MPEG-1/2/2.5 Audio Frame Header
23
+ { bytes: Buffer.from([0xFF, 0xFB]), offset: 0, name: 'MP3 MPEG-2 Layer III' },
24
+ { bytes: Buffer.from([0xFF, 0xFA]), offset: 0, name: 'MP3 MPEG-1 Layer III' },
25
+ // ID3 Tag (v2.x and v2.4)
26
+ { bytes: Buffer.from([0x49, 0x44, 0x33]), offset: 0, name: 'ID3v2 Tag' } // "ID3"
27
+ ]
28
+ },
29
+ wav: {
30
+ extension: '.wav',
31
+ magicNumbers: [
32
+ { bytes: Buffer.from('RIFF'), offset: 0, name: 'RIFF Header' },
33
+ { bytes: Buffer.from('WAVE'), offset: 8, name: 'WAV Format' }
34
+ ]
35
+ },
36
+ ogg: {
37
+ extension: '.ogg',
38
+ magicNumbers: [
39
+ { bytes: Buffer.from('OggS'), offset: 0, name: 'OggS Header' }
40
+ ]
41
+ },
42
+ m4a: {
43
+ extension: '.m4a',
44
+ magicNumbers: [
45
+ { bytes: Buffer.from('ftypisom'), offset: 4, name: 'M4A ISO Base Media' },
46
+ { bytes: Buffer.from('ftypM4A'), offset: 4, name: 'M4A Apple iTunes' }
47
+ ]
48
+ }
49
+ };
50
+
51
+ const SUPPORTED_EXTENSIONS = Object.values(AUDIO_FORMATS).map(f => f.extension);
52
+
53
+ /**
54
+ * Story 4.2: Detect audio format by checking magic numbers in file header
55
+ *
56
+ * Reads the first 256 bytes of the file to check magic number signatures.
57
+ * Does NOT require the file to have the correct extension matching format.
58
+ *
59
+ * @param {string} filePath - Path to audio file (must already be validated with isPathSafe)
60
+ * @returns {Object} { isValid: boolean, error: string|null, format: string|null, detectedFormat: string|null }
61
+ *
62
+ * Object properties:
63
+ * - isValid: true if file is valid audio format
64
+ * - error: null on success, error message on failure
65
+ * - format: detected format ('mp3', 'wav', 'ogg', 'm4a') or null
66
+ * - detectedFormat: human-readable format name (e.g., "MP3 MPEG-1 Layer III")
67
+ */
68
+ function detectAudioFormat(filePath) {
69
+ try {
70
+ if (!filePath || typeof filePath !== 'string') {
71
+ return {
72
+ isValid: false,
73
+ error: 'File path must be a non-empty string',
74
+ format: null,
75
+ detectedFormat: null
76
+ };
77
+ }
78
+
79
+ // Check file exists and is readable
80
+ if (!fs.existsSync(filePath)) {
81
+ return {
82
+ isValid: false,
83
+ error: 'File does not exist',
84
+ format: null,
85
+ detectedFormat: null
86
+ };
87
+ }
88
+
89
+ const stats = fs.statSync(filePath);
90
+ if (!stats.isFile()) {
91
+ return {
92
+ isValid: false,
93
+ error: 'Path must be a regular file',
94
+ format: null,
95
+ detectedFormat: null
96
+ };
97
+ }
98
+
99
+ // Check minimum file size (at least 12 bytes for WAV format check)
100
+ if (stats.size < 12) {
101
+ return {
102
+ isValid: false,
103
+ error: 'File is too small to be a valid audio file (minimum 12 bytes)',
104
+ format: null,
105
+ detectedFormat: null
106
+ };
107
+ }
108
+
109
+ // Read first 256 bytes for magic number detection
110
+ const buffer = Buffer.alloc(256);
111
+ const fd = fs.openSync(filePath, 'r');
112
+ try {
113
+ fs.readSync(fd, buffer, 0, 256, 0);
114
+ } finally {
115
+ fs.closeSync(fd);
116
+ }
117
+
118
+ // Check each format's magic numbers
119
+ for (const [formatKey, formatInfo] of Object.entries(AUDIO_FORMATS)) {
120
+ for (const magicInfo of formatInfo.magicNumbers) {
121
+ // Check if magic bytes exist at the specified offset
122
+ const isMatch = buffer.subarray(magicInfo.offset, magicInfo.offset + magicInfo.bytes.length)
123
+ .equals(magicInfo.bytes);
124
+
125
+ if (isMatch) {
126
+ return {
127
+ isValid: true,
128
+ error: null,
129
+ format: formatKey,
130
+ detectedFormat: magicInfo.name
131
+ };
132
+ }
133
+ }
134
+ }
135
+
136
+ // No magic numbers matched
137
+ return {
138
+ isValid: false,
139
+ error: `Unsupported audio format. Supported formats: ${SUPPORTED_EXTENSIONS.join(', ')}`,
140
+ format: null,
141
+ detectedFormat: null
142
+ };
143
+
144
+ } catch (err) {
145
+ return {
146
+ isValid: false,
147
+ error: `Error detecting audio format: ${err.message}`,
148
+ format: null,
149
+ detectedFormat: null
150
+ };
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Story 4.2: Validate that file extension matches detected audio format
156
+ *
157
+ * Ensures the file extension is correct for the detected format.
158
+ * This prevents accidental misnamed files (e.g., .txt file with MP3 content).
159
+ *
160
+ * @param {string} filePath - Path to audio file
161
+ * @param {string} detectedFormat - Format from detectAudioFormat() result
162
+ * @returns {Object} { isValid: boolean, error: string|null }
163
+ */
164
+ function validateFileExtension(filePath, detectedFormat) {
165
+ try {
166
+ if (!filePath || !detectedFormat) {
167
+ return {
168
+ isValid: false,
169
+ error: 'File path and detected format are required'
170
+ };
171
+ }
172
+
173
+ const ext = path.extname(filePath).toLowerCase();
174
+ const expectedExt = AUDIO_FORMATS[detectedFormat]?.extension;
175
+
176
+ if (!expectedExt) {
177
+ return {
178
+ isValid: false,
179
+ error: `Unknown detected format: ${detectedFormat}`
180
+ };
181
+ }
182
+
183
+ if (ext === expectedExt) {
184
+ return {
185
+ isValid: true,
186
+ error: null
187
+ };
188
+ }
189
+
190
+ // Extension doesn't match - return specific error
191
+ return {
192
+ isValid: false,
193
+ error: `File extension (${ext || 'none'}) doesn't match actual format (.${detectedFormat})`
194
+ };
195
+
196
+ } catch (err) {
197
+ return {
198
+ isValid: false,
199
+ error: `Error validating file extension: ${err.message}`
200
+ };
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Story 4.2: Comprehensive audio file validation
206
+ *
207
+ * Combines format detection and extension validation in one call.
208
+ * Returns false only if BOTH format is unsupported AND extension is wrong.
209
+ * Returns true if format is valid (even if extension differs).
210
+ *
211
+ * @param {string} filePath - Path to audio file
212
+ * @param {Object} options - Validation options
213
+ * @param {boolean} options.strictExtension - If true, reject files with wrong extension (default: false)
214
+ * @returns {Object} { isValid: boolean, error: string|null, format: string|null, detectedFormat: string|null, extensionMatch: boolean }
215
+ */
216
+ function validateAudioFile(filePath, options = {}) {
217
+ const { strictExtension = false } = options;
218
+
219
+ try {
220
+ // Detect format first
221
+ const formatResult = detectAudioFormat(filePath);
222
+
223
+ if (!formatResult.isValid) {
224
+ return {
225
+ isValid: false,
226
+ error: formatResult.error,
227
+ format: null,
228
+ detectedFormat: null,
229
+ extensionMatch: false
230
+ };
231
+ }
232
+
233
+ // Check extension if strict mode enabled
234
+ if (strictExtension) {
235
+ const extResult = validateFileExtension(filePath, formatResult.format);
236
+ if (!extResult.isValid) {
237
+ return {
238
+ isValid: false,
239
+ error: extResult.error,
240
+ format: formatResult.format,
241
+ detectedFormat: formatResult.detectedFormat,
242
+ extensionMatch: false
243
+ };
244
+ }
245
+ }
246
+
247
+ // Format is valid - check if extension matches (informational)
248
+ const ext = path.extname(filePath).toLowerCase();
249
+ const expectedExt = AUDIO_FORMATS[formatResult.format]?.extension;
250
+ const extensionMatch = ext === expectedExt;
251
+
252
+ return {
253
+ isValid: true,
254
+ error: null,
255
+ format: formatResult.format,
256
+ detectedFormat: formatResult.detectedFormat,
257
+ extensionMatch
258
+ };
259
+
260
+ } catch (err) {
261
+ return {
262
+ isValid: false,
263
+ error: `Unexpected validation error: ${err.message}`,
264
+ format: null,
265
+ detectedFormat: null,
266
+ extensionMatch: false
267
+ };
268
+ }
269
+ }
270
+
271
+ export {
272
+ detectAudioFormat,
273
+ validateFileExtension,
274
+ validateAudioFile,
275
+ AUDIO_FORMATS,
276
+ SUPPORTED_EXTENSIONS
277
+ };