mediacript 1.0.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.
package/QUICK_START.md ADDED
@@ -0,0 +1,154 @@
1
+ # 🚀 Quick Start Guide
2
+
3
+ ## First Time
4
+
5
+ 1. **Install dependencies**:
6
+ ```bash
7
+ npm install
8
+ ```
9
+
10
+ 2. **Run the CLI**:
11
+ ```bash
12
+ npm start
13
+ ```
14
+
15
+ 3. **Configure your API Keys** (when prompted):
16
+ - Groq (recommended): https://console.groq.com
17
+ - OpenAI: https://platform.openai.com
18
+
19
+ ## Available Workflows
20
+
21
+ ### 🎬 For Videos
22
+
23
+ #### Complete Pipeline
24
+ ```
25
+ Convert video → Extract audio → Transcribe
26
+ ```
27
+ Best for: Fully processing a video, optimizing and transcribing
28
+
29
+ #### Extract and Transcribe
30
+ ```
31
+ Extract audio → Transcribe
32
+ ```
33
+ Best for: Transcribing videos without converting them
34
+
35
+ #### Only Convert
36
+ ```
37
+ Convert video (H.264/AAC)
38
+ ```
39
+ Best for: Optimizing videos for web/streaming
40
+
41
+ #### Only Extract Audio
42
+ ```
43
+ Extract audio (MP3)
44
+ ```
45
+ Best for: Extracting soundtrack from videos
46
+
47
+ ### 🎵 For Audio
48
+
49
+ #### Convert and Transcribe
50
+ ```
51
+ Convert audio → Transcribe
52
+ ```
53
+ Best for: Processing audio in non-optimized format
54
+
55
+ #### Only Transcribe
56
+ ```
57
+ Transcribe audio
58
+ ```
59
+ Best for: Transcribing podcasts, interviews, etc.
60
+
61
+ #### Only Convert
62
+ ```
63
+ Convert audio (MP3)
64
+ ```
65
+ Best for: Standardizing audio format
66
+
67
+ ## Practical Examples
68
+
69
+ ### Transcribe a lecture video
70
+ ```bash
71
+ # Place lecture_video.mp4 in the folder
72
+ npm start
73
+ # Select: "Extract audio from video + Transcribe"
74
+ # Result: lecture_video_audio.mp3 and lecture_video_audio.txt
75
+ ```
76
+
77
+ ### Convert podcast and transcribe
78
+ ```bash
79
+ # Place podcast.wav in the folder
80
+ npm start
81
+ # Select: "Convert audio + Transcribe"
82
+ # Result: podcast_converted.mp3 and podcast_converted.txt
83
+ ```
84
+
85
+ ### Complete video pipeline
86
+ ```bash
87
+ # Place presentation.mkv in the folder
88
+ npm start
89
+ # Select: "Convert video + Extract audio + Transcribe"
90
+ # Result:
91
+ # - presentation_converted.mp4 (optimized video)
92
+ # - presentation_converted_audio.mp3
93
+ # - presentation_converted_audio.txt
94
+ ```
95
+
96
+ ## Tips
97
+
98
+ ### 💡 Transcription
99
+ - **Groq is faster** and cheaper than OpenAI
100
+ - Configure both keys to have automatic fallback
101
+ - Transcription works best with clear audio
102
+
103
+ ### 📊 Tracking
104
+ - Progress is displayed in real-time
105
+ - Each step shows elapsed time
106
+ - State is saved in `.workflow-state.json`
107
+
108
+ ### 🗂️ Organization
109
+ - Generated files stay in the same directory as the original
110
+ - Suffixes are automatically added:
111
+ - `_converted` for conversions
112
+ - `_audio` for extracted audio
113
+ - `.txt` for transcriptions
114
+
115
+ ### ⚡ Performance
116
+ - Video conversions can take time (depends on size)
117
+ - Audio extraction is fast
118
+ - Transcription depends on audio size and service
119
+
120
+ ## Useful Commands
121
+
122
+ ```bash
123
+ # Development mode (auto-reload)
124
+ npm run dev
125
+
126
+ # Compile for production
127
+ npm run build
128
+
129
+ # Simple converter (old)
130
+ npm run convert
131
+ ```
132
+
133
+ ## Troubleshooting
134
+
135
+ ### "FFmpeg not found"
136
+ - Install FFmpeg following the README instructions
137
+ - Restart the terminal after installing
138
+
139
+ ### "No API key configured"
140
+ - Run again and configure when prompted
141
+ - Or manually edit: `~/.config/ffmpeg-simple-converter/config.json`
142
+
143
+ ### Transcription failed
144
+ - Check if you have credits/quota in the API
145
+ - Try the other service (Groq or OpenAI)
146
+ - Check internet connection
147
+
148
+ ### File doesn't appear in the list
149
+ - Check if the extension is supported
150
+ - Make sure you're in the correct directory
151
+
152
+ ## 📚 More Information
153
+
154
+ See the complete [README.md](README.md) for technical details and advanced topics.
package/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # FFmpeg Simple Converter
2
+
3
+ Powerful and flexible CLI for converting videos/audio and AI transcription, working on Linux, Mac, and Windows.
4
+
5
+ ## 🌟 Features
6
+
7
+ - ✅ **Cross-platform**: Works on Linux, macOS, and Windows
8
+ - 🔄 **Multi-Step Workflow**: Combine multiple operations in a single flow
9
+ - 🎙️ **AI Transcription**: Support for Groq (fast) and OpenAI Whisper
10
+ - 💾 **State Management**: Saves progress of each workflow step
11
+ - 🔑 **Persistent Configuration**: API keys saved locally and securely
12
+ - 📊 **Visual Progress**: Track each step of the process
13
+
14
+ ## 📋 Requirements
15
+
16
+ - **Node.js** `>= 16`
17
+ - **FFmpeg** installed and available in PATH
18
+
19
+ ### Installing FFmpeg
20
+
21
+ #### Windows
22
+ ```bash
23
+ # With Chocolatey
24
+ choco install ffmpeg
25
+
26
+ # With Scoop
27
+ scoop install ffmpeg
28
+ ```
29
+
30
+ #### macOS
31
+ ```bash
32
+ brew install ffmpeg
33
+ ```
34
+
35
+ #### Linux
36
+ ```bash
37
+ # Ubuntu/Debian
38
+ sudo apt update && sudo apt install ffmpeg
39
+
40
+ # Fedora
41
+ sudo dnf install ffmpeg
42
+
43
+ # Arch Linux
44
+ sudo pacman -S ffmpeg
45
+ ```
46
+
47
+ Verify installation:
48
+ ```bash
49
+ ffmpeg -version
50
+ ```
51
+
52
+ ## 🚀 Installation
53
+
54
+ ```bash
55
+ npm install
56
+ ```
57
+
58
+ ## 💡 Usage
59
+
60
+ ### Interactive Mode (Recommended)
61
+
62
+ ```bash
63
+ npm start
64
+ ```
65
+
66
+ The CLI will:
67
+ 1. ✅ Check if FFmpeg is installed
68
+ 2. 🔑 Request API keys on first run (optional)
69
+ 3. 📁 List media files in the current directory
70
+ 4. 🎯 Allow you to choose the desired workflow
71
+
72
+ ### Available Workflows
73
+
74
+ #### For Videos 🎬
75
+ - **Convert video + Extract audio + Transcribe**: Complete pipeline
76
+ - **Extract audio from video + Transcribe**: To transcribe videos
77
+ - **Only convert video**: Optimize video (H.264/AAC)
78
+ - **Only extract audio from video**: Extract audio as MP3
79
+
80
+ #### For Audio 🎵
81
+ - **Convert audio + Transcribe**: Convert and transcribe
82
+ - **Only transcribe audio**: Direct transcription
83
+ - **Only convert audio**: Convert to MP3
84
+
85
+ ## 🔑 API Keys Configuration
86
+
87
+ ### First Run
88
+
89
+ The first time you run it, you'll be asked if you want to configure your API keys:
90
+
91
+ ```
92
+ ⚠️ No API key found.
93
+ ? Do you want to configure your API keys now? (Y/n)
94
+
95
+ 🔑 Configure your API keys (optional - press Enter to skip)
96
+
97
+ ? Groq API Key (recommended - faster): sk-proj-...
98
+ ? OpenAI API Key: sk-...
99
+ ```
100
+
101
+ ### Where Keys Are Saved
102
+
103
+ - **Linux/Mac**: `~/.config/ffmpeg-simple-converter/config.json`
104
+ - **Windows**: `%APPDATA%/ffmpeg-simple-converter/config.json`
105
+
106
+ ### Getting API Keys
107
+
108
+ #### Groq (Recommended - Faster and Cheaper)
109
+ 1. Visit: https://console.groq.com
110
+ 2. Create a free account
111
+ 3. Generate an API key in "API Keys"
112
+
113
+ #### OpenAI
114
+ 1. Visit: https://platform.openai.com
115
+ 2. Create an account
116
+ 3. Add credits
117
+ 4. Generate an API key in "API Keys"
118
+
119
+ ### Transcription Priority
120
+
121
+ The system automatically tries in the following order:
122
+ 1. **Groq** (if configured) - faster and cheaper
123
+ 2. **OpenAI** (fallback) - if Groq fails or is not configured
124
+
125
+ ## 📊 Usage Example
126
+
127
+ ```bash
128
+ $ npm start
129
+
130
+ 🎬 FFmpeg Simple Converter - Multi-Step Workflow
131
+
132
+ ✓ FFmpeg is installed (version: 6.0)
133
+
134
+ 📁 Found 3 media file(s)
135
+
136
+ ? Select file:
137
+ 🎬 lecture_video.mp4
138
+ ❯ 🎵 podcast.mp3
139
+ 🎬 presentation.mkv
140
+
141
+ ? Select what you want to do:
142
+ ❯ 🎬 Convert video + Extract audio + Transcribe
143
+ 🎬 Extract audio from video + Transcribe
144
+ 🎵 Convert audio + Transcribe
145
+ 🎙️ Only transcribe audio
146
+
147
+ 🚀 Starting workflow: Convert video + Extract audio + Transcribe
148
+ 📁 Input file: lecture_video.mp4
149
+
150
+ [1/3] Convert video...
151
+ 🎬 Converting video to optimized format...
152
+ ✓ Video converted: lecture_video_converted.mp4
153
+
154
+ [2/3] Extract audio...
155
+ 🎵 Extracting audio from video...
156
+ ✓ Audio extracted: lecture_video_converted_audio.mp3
157
+
158
+ [3/3] Transcribe audio...
159
+ 🎙️ Transcribing: lecture_video_converted_audio.mp3
160
+ 📡 Trying Groq Whisper (fast)...
161
+ ✓ Transcription completed with Groq
162
+ ✓ Transcription saved: lecture_video_converted_audio.txt
163
+
164
+ 📊 Workflow Progress:
165
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
166
+ ✓ 1. Convert video (12.3s)
167
+ ✓ 2. Extract audio (3.1s)
168
+ ✓ 3. Transcribe audio (8.7s)
169
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
170
+
171
+ 📦 Generated files:
172
+ • Video: lecture_video_converted.mp4
173
+ • Audio: lecture_video_converted_audio.mp3
174
+ • Transcription: lecture_video_converted_audio.txt
175
+ ```
176
+
177
+ ## 🗂️ Project Structure
178
+
179
+ ```
180
+ src/
181
+ ├── config/ # Configuration and API keys management
182
+ ├── transcript/ # Transcription modules (Groq and OpenAI)
183
+ ├── types/ # TypeScript definitions
184
+ ├── utils/ # Utilities (ffmpeg, files, etc)
185
+ ├── workflow/ # Workflow management system
186
+ └── index.ts # Main CLI
187
+ ```
188
+
189
+ ## 🔧 Available Scripts
190
+
191
+ ```bash
192
+ npm start # Run the interactive CLI
193
+ npm run build # Compile TypeScript to JavaScript
194
+ npm run dev # Development mode with watch
195
+ npm run convert # Run the old converter (convert.js)
196
+ ```
197
+
198
+ ## 📦 Supported Formats
199
+
200
+ ### Audio
201
+ `.ogg`, `.wav`, `.mp3`, `.m4a`, `.aac`, `.flac`
202
+
203
+ ### Video
204
+ `.mp4`, `.mov`, `.mkv`, `.webm`, `.avi`
205
+
206
+ ## 🛠️ State Management
207
+
208
+ Each workflow saves its state to `.workflow-state.json` in the output directory:
209
+
210
+ ```json
211
+ {
212
+ "steps": [
213
+ {
214
+ "id": "step-0",
215
+ "name": "Convert video",
216
+ "status": "completed",
217
+ "startTime": 1675436400000,
218
+ "endTime": 1675436412300
219
+ }
220
+ ],
221
+ "intermediateFiles": {
222
+ "convertedVideo": "video_converted.mp4",
223
+ "extractedAudio": "video_audio.mp3",
224
+ "transcriptionText": "video_audio.txt"
225
+ }
226
+ }
227
+ ```
228
+
229
+ ## 🤝 Contributing
230
+
231
+ Contributions are welcome! Feel free to open issues and pull requests.
232
+
233
+ ## 📄 License
234
+
235
+ MIT
236
+
237
+ ## 🙏 Acknowledgments
238
+
239
+ - FFmpeg for the amazing tool
240
+ - OpenAI and Groq for transcription services
package/cli.mjs ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Importa e executa o CLI compilado
4
+ import('../dist/index.js').catch((error) => {
5
+ console.error('Erro ao carregar o CLI:', error)
6
+ process.exit(1)
7
+ })
package/convert.js ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawn } = require('child_process');
6
+ const inquirer = require('inquirer');
7
+
8
+ const AUDIO_EXTS = new Set(['.ogg', '.wav', '.mp3', '.m4a', '.aac', '.flac']);
9
+ const VIDEO_EXTS = new Set(['.mp4', '.mov', '.mkv', '.webm', '.avi']);
10
+
11
+ function isFile(p) {
12
+ try {
13
+ return fs.statSync(p).isFile();
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ function detectKindByExt(ext) {
20
+ const lower = ext.toLowerCase();
21
+ if (AUDIO_EXTS.has(lower)) return 'audio';
22
+ if (VIDEO_EXTS.has(lower)) return 'video';
23
+ return 'unknown';
24
+ }
25
+
26
+ function listCandidateFiles(dir) {
27
+ return fs
28
+ .readdirSync(dir)
29
+ .map((name) => ({ name, fullPath: path.join(dir, name) }))
30
+ .filter((f) => isFile(f.fullPath))
31
+ .filter((f) => {
32
+ const ext = path.extname(f.name).toLowerCase();
33
+ return AUDIO_EXTS.has(ext) || VIDEO_EXTS.has(ext);
34
+ })
35
+ .sort((a, b) => a.name.localeCompare(b.name));
36
+ }
37
+
38
+ function uniqueOutputPath(dir, baseName, extWithDot) {
39
+ let candidate = path.join(dir, `${baseName}${extWithDot}`);
40
+ if (!fs.existsSync(candidate)) return candidate;
41
+
42
+ for (let i = 1; i < 10_000; i++) {
43
+ candidate = path.join(dir, `${baseName}_${i}${extWithDot}`);
44
+ if (!fs.existsSync(candidate)) return candidate;
45
+ }
46
+
47
+ throw new Error('Nao foi possivel gerar um nome de saida unico.');
48
+ }
49
+
50
+ function buildPresets() {
51
+ /**
52
+ * Preset shape:
53
+ * - id: string
54
+ * - label: string
55
+ * - kinds: Array<'audio'|'video'>
56
+ * - build({ inputPath, outputPath }): { cmd: 'ffmpeg', args: string[] }
57
+ */
58
+ return [
59
+ {
60
+ id: 'audio_mp3_q2',
61
+ label: 'Audio → MP3 (boa qualidade, q:a 2)',
62
+ kinds: ['audio'],
63
+ outputExt: '.mp3',
64
+ build: ({ inputPath, outputPath }) => ({
65
+ cmd: 'ffmpeg',
66
+ args: ['-hide_banner', '-n', '-i', inputPath, '-c:a', 'libmp3lame', '-q:a', '2', outputPath]
67
+ })
68
+ },
69
+ {
70
+ id: 'audio_mp3_small',
71
+ label: 'Audio → MP3 (menor, q:a 5)',
72
+ kinds: ['audio'],
73
+ outputExt: '.mp3',
74
+ build: ({ inputPath, outputPath }) => ({
75
+ cmd: 'ffmpeg',
76
+ args: ['-hide_banner', '-n', '-i', inputPath, '-c:a', 'libmp3lame', '-q:a', '5', outputPath]
77
+ })
78
+ },
79
+ {
80
+ id: 'video_1080p_medium',
81
+ label: 'Video → MP4 1080p (razoavel/menor: preset medium, CRF 28)',
82
+ kinds: ['video'],
83
+ outputExt: '.mp4',
84
+ build: ({ inputPath, outputPath }) => ({
85
+ cmd: 'ffmpeg',
86
+ args: [
87
+ '-hide_banner',
88
+ '-n',
89
+ '-i',
90
+ inputPath,
91
+ '-vf',
92
+ "scale='min(1080,iw)':-2",
93
+ '-c:v',
94
+ 'libx264',
95
+ '-profile:v',
96
+ 'baseline',
97
+ '-level',
98
+ '3.1',
99
+ '-preset',
100
+ 'medium',
101
+ '-crf',
102
+ '28',
103
+ '-r',
104
+ '30',
105
+ '-c:a',
106
+ 'aac',
107
+ '-b:a',
108
+ '128k',
109
+ '-movflags',
110
+ '+faststart',
111
+ outputPath
112
+ ]
113
+ })
114
+ },
115
+ {
116
+ id: 'video_1080p_better',
117
+ label: 'Video → MP4 1080p (melhor/maior: preset slow, CRF 23)',
118
+ kinds: ['video'],
119
+ outputExt: '.mp4',
120
+ build: ({ inputPath, outputPath }) => ({
121
+ cmd: 'ffmpeg',
122
+ args: [
123
+ '-hide_banner',
124
+ '-n',
125
+ '-i',
126
+ inputPath,
127
+ '-vf',
128
+ "scale='min(1080,iw)':-2",
129
+ '-c:v',
130
+ 'libx264',
131
+ '-profile:v',
132
+ 'baseline',
133
+ '-level',
134
+ '3.1',
135
+ '-preset',
136
+ 'slow',
137
+ '-crf',
138
+ '23',
139
+ '-r',
140
+ '30',
141
+ '-c:a',
142
+ 'aac',
143
+ '-b:a',
144
+ '128k',
145
+ '-movflags',
146
+ '+faststart',
147
+ outputPath
148
+ ]
149
+ })
150
+ }
151
+ ];
152
+ }
153
+
154
+ function spawnAndStream(cmd, args) {
155
+ return new Promise((resolve, reject) => {
156
+ const child = spawn(cmd, args, { stdio: 'inherit' });
157
+ child.on('error', reject);
158
+ child.on('close', (code) => {
159
+ if (code === 0) resolve();
160
+ else reject(new Error(`${cmd} saiu com codigo ${code}`));
161
+ });
162
+ });
163
+ }
164
+
165
+ async function main() {
166
+ const cwd = process.cwd();
167
+ const files = listCandidateFiles(cwd);
168
+
169
+ if (files.length === 0) {
170
+ console.log('Nenhum arquivo de audio/video suportado encontrado na pasta atual.');
171
+ console.log('Extensoes de audio:', Array.from(AUDIO_EXTS).join(', '));
172
+ console.log('Extensoes de video:', Array.from(VIDEO_EXTS).join(', '));
173
+ process.exit(1);
174
+ }
175
+
176
+ const { selectedFiles } = await inquirer.prompt([
177
+ {
178
+ type: 'checkbox',
179
+ name: 'selectedFiles',
180
+ message: 'Selecione os arquivos para converter:',
181
+ pageSize: 15,
182
+ choices: files.map((f) => ({
183
+ name: f.name,
184
+ value: f.fullPath
185
+ })),
186
+ validate: (arr) => (arr && arr.length > 0 ? true : 'Selecione pelo menos 1 arquivo.')
187
+ }
188
+ ]);
189
+
190
+ const presets = buildPresets();
191
+
192
+ const kindByInput = new Map();
193
+ const selectedKinds = new Set();
194
+ for (const inputPath of selectedFiles) {
195
+ const kind = detectKindByExt(path.extname(inputPath));
196
+ kindByInput.set(inputPath, kind);
197
+ selectedKinds.add(kind);
198
+ }
199
+
200
+ if (selectedKinds.has('unknown')) {
201
+ console.log('Selecao contem arquivos com extensao nao suportada.');
202
+ process.exit(1);
203
+ }
204
+
205
+ // Escolhe 1 preset por tipo detectado (audio/video)
206
+ const presetByKind = new Map();
207
+ for (const kind of selectedKinds) {
208
+ const applicable = presets.filter((p) => p.kinds.includes(kind));
209
+ if (applicable.length === 0) {
210
+ console.log(`Nao ha presets disponiveis para tipo: ${kind}`);
211
+ process.exit(1);
212
+ }
213
+
214
+ const { presetId } = await inquirer.prompt([
215
+ {
216
+ type: 'list',
217
+ name: 'presetId',
218
+ message: `Selecione o preset para ${kind}:`,
219
+ choices: applicable.map((p) => ({ name: p.label, value: p.id }))
220
+ }
221
+ ]);
222
+
223
+ const preset = presets.find((p) => p.id === presetId);
224
+ if (!preset) throw new Error('Preset selecionado nao encontrado.');
225
+ presetByKind.set(kind, preset);
226
+ }
227
+
228
+ for (const inputPath of selectedFiles) {
229
+ const parsed = path.parse(inputPath);
230
+ const inputExt = parsed.ext.toLowerCase();
231
+ const baseName = parsed.name;
232
+
233
+ const kind = kindByInput.get(inputPath);
234
+ const preset = presetByKind.get(kind);
235
+ if (!preset) throw new Error(`Preset nao encontrado para tipo: ${kind}`);
236
+
237
+ // Evita sobrescrever quando a extensao final for igual
238
+ const outputBase = inputExt === preset.outputExt ? `${baseName}_converted` : baseName;
239
+ const outputPath = uniqueOutputPath(parsed.dir, outputBase, preset.outputExt);
240
+
241
+ const { cmd, args } = preset.build({ inputPath, outputPath });
242
+
243
+ console.log('\n----------------------------------------');
244
+ console.log('Entrada:', path.basename(inputPath));
245
+ console.log('Saida :', path.basename(outputPath));
246
+ console.log('Comando:', [cmd, ...args].join(' '));
247
+
248
+ await spawnAndStream(cmd, args);
249
+ }
250
+
251
+ console.log('\nConcluido.');
252
+ }
253
+
254
+ main().catch((err) => {
255
+ console.error('\nErro:', err && err.message ? err.message : err);
256
+ process.exit(1);
257
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "mediacript",
3
+ "version": "1.0.0",
4
+ "description": "CLI to convert media files to performat formats and extract text using Groq and OpenAI's Whisper API",
5
+ "type": "module",
6
+ "bin": {
7
+ "mediacript": "./cli.mjs",
8
+ "mediacript-convert": "./convert.js"
9
+ },
10
+ "scripts": {
11
+ "convert": "node convert.js",
12
+ "start": "node --loader ts-node/esm src/index.ts",
13
+ "build": "tsc && chmod +x dist/index.js && chmod +x cli.mjs",
14
+ "dev": "node --loader ts-node/esm --watch src/index.ts",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "engines": {
18
+ "node": ">=16"
19
+ },
20
+ "dependencies": {
21
+ "axios": "^1.13.4",
22
+ "dotenv": "^17.2.3",
23
+ "form-data": "^4.0.5",
24
+ "inquirer": "^8.2.6",
25
+ "openai": "^4.104.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/inquirer": "^8.2.12",
29
+ "@types/node": "^25.0.10",
30
+ "ts-node": "^10.9.2",
31
+ "typescript": "^5.9.3"
32
+ }
33
+ }