agentvibes 5.11.2 → 5.12.0-alpha.0

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 (97) hide show
  1. package/.agentvibes/config.json +1 -15
  2. package/.claude/activation-instructions +54 -54
  3. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  4. package/.claude/commands/agent-vibes-rdp.md +24 -24
  5. package/.claude/config/audio-effects.cfg +5 -5
  6. package/.claude/config/background-music-enabled.txt +1 -1
  7. package/.claude/docs/TERMUX_SETUP.md +408 -408
  8. package/.claude/github-star-reminder.txt +1 -1
  9. package/.claude/hooks/audio-processor.sh +1 -1
  10. package/.claude/hooks/background-music-manager.sh +2 -1
  11. package/.claude/hooks/bmad-speak-enhanced.sh +37 -22
  12. package/.claude/hooks/bmad-speak.sh +26 -8
  13. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +1 -1
  14. package/.claude/hooks/play-tts-kokoro.sh +9 -0
  15. package/.claude/hooks/play-tts-piper.sh +13 -1
  16. package/.claude/hooks/play-tts-ssh-remote.sh +78 -5
  17. package/.claude/hooks/play-tts-termux-ssh.sh +1 -1
  18. package/.claude/hooks/play-tts.sh +85 -4
  19. package/.claude/hooks-windows/background-music-manager.ps1 +2 -1
  20. package/.claude/hooks-windows/play-tts-kokoro.ps1 +14 -0
  21. package/.claude/hooks-windows/play-tts.ps1 +163 -30
  22. package/.claude/hooks-windows/voice-manager-windows.ps1 +101 -1
  23. package/.clawdbot/README.md +105 -105
  24. package/LICENSE +190 -190
  25. package/RELEASE_NOTES.md +29 -0
  26. package/WINDOWS-SETUP.md +208 -208
  27. package/bin/agent-vibes +39 -39
  28. package/bin/mcp-server.js +121 -121
  29. package/bin/mcp-server.sh +6 -19
  30. package/bin/resolve-utterance.js +137 -0
  31. package/bin/test-bmad-pr +78 -78
  32. package/mcp-server/QUICK_START.md +203 -203
  33. package/mcp-server/README.md +345 -345
  34. package/mcp-server/examples/claude_desktop_config.json +11 -11
  35. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  36. package/mcp-server/examples/custom_instructions.md +169 -169
  37. package/mcp-server/pyproject.toml +52 -52
  38. package/mcp-server/requirements.txt +2 -2
  39. package/mcp-server/server.py +280 -99
  40. package/mcp-server/test_mcp_correctness.py +486 -0
  41. package/mcp-server/test_server.py +395 -395
  42. package/mcp-server/test_windows_script_parity.py +341 -336
  43. package/package.json +3 -2
  44. package/setup-windows.ps1 +867 -815
  45. package/src/cli/list-personalities.js +110 -110
  46. package/src/cli/list-voices.js +114 -114
  47. package/src/commands/bmad-voices.js +394 -394
  48. package/src/console/app.js +13 -2
  49. package/src/console/brand-colors.js +13 -13
  50. package/src/console/constants/personalities.js +44 -44
  51. package/src/console/tabs/agents-tab.js +82 -9
  52. package/src/console/tabs/help-tab.js +314 -314
  53. package/src/console/tabs/music-tab.js +7 -1
  54. package/src/console/tabs/readme-tab.js +272 -272
  55. package/src/console/tabs/settings-tab.js +29 -1
  56. package/src/console/tabs/setup-tab.js +4 -4
  57. package/src/console/widgets/destroy-list.js +25 -25
  58. package/src/console/widgets/notice.js +55 -55
  59. package/src/console/widgets/track-picker.js +3 -3
  60. package/src/i18n/de.js +0 -1
  61. package/src/i18n/en.js +0 -1
  62. package/src/i18n/es.js +0 -1
  63. package/src/i18n/fr.js +0 -1
  64. package/src/i18n/hi.js +0 -1
  65. package/src/i18n/ja.js +0 -1
  66. package/src/i18n/ko.js +0 -1
  67. package/src/i18n/pt.js +0 -1
  68. package/src/i18n/zh-CN.js +0 -1
  69. package/src/installer/language-screen.js +31 -31
  70. package/src/installer/music-file-input.js +304 -304
  71. package/src/installer.js +205 -15
  72. package/src/services/config-service.js +264 -264
  73. package/src/services/language-service.js +47 -47
  74. package/src/services/llm-provider-service.js +7 -7
  75. package/src/services/utterance-loader.js +280 -0
  76. package/src/services/utterance-resolver.js +526 -0
  77. package/src/utils/audio-format-validator.js +277 -277
  78. package/src/utils/dependency-checker.js +469 -469
  79. package/src/utils/file-ownership-verifier.js +358 -358
  80. package/src/utils/music-file-validator.js +285 -285
  81. package/src/utils/preview-list-prompt.js +144 -144
  82. package/src/utils/secure-music-storage.js +412 -412
  83. package/templates/agentvibes-receiver.sh +74 -12
  84. package/voice-assignments.json +8245 -0
  85. package/.agentvibes/install-manifest.json +0 -342
  86. package/.claude/config/background-music-position.txt +0 -27
  87. package/.claude/config/background-music-volume.txt +0 -1
  88. package/.claude/config/background-music.cfg +0 -1
  89. package/.claude/config/background-music.txt +0 -1
  90. package/.claude/config/language.txt +0 -1
  91. package/.claude/config/personality.txt +0 -1
  92. package/.claude/config/tts-speech-rate.txt +0 -1
  93. package/.claude/config/tts-verbosity.txt +0 -1
  94. package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
  95. package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
  96. package/.claude/piper-voices-dir.txt +0 -1
  97. package/.mcp.json +0 -22
@@ -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
+ };