videonut 1.0.1 → 1.1.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 (79) hide show
  1. package/.antigravity/config.toml +8 -0
  2. package/.claude/commands/archivist.toml +12 -0
  3. package/.claude/commands/director.toml +12 -0
  4. package/.claude/commands/eic.toml +12 -0
  5. package/.claude/commands/investigator.toml +12 -0
  6. package/.claude/commands/prompt.toml +12 -0
  7. package/.claude/commands/scavenger.toml +12 -0
  8. package/.claude/commands/scout.toml +12 -0
  9. package/.claude/commands/scriptwriter.toml +12 -0
  10. package/.claude/commands/seo.toml +12 -0
  11. package/.claude/commands/thumbnail.toml +12 -0
  12. package/.claude/commands/topic_scout.toml +12 -0
  13. package/.gemini/commands/archivist.toml +12 -0
  14. package/.gemini/commands/director.toml +12 -0
  15. package/.gemini/commands/eic.toml +12 -0
  16. package/.gemini/commands/investigator.toml +12 -0
  17. package/.gemini/commands/prompt.toml +12 -0
  18. package/.gemini/commands/scavenger.toml +12 -0
  19. package/.gemini/commands/scout.toml +12 -0
  20. package/.gemini/commands/scriptwriter.toml +12 -0
  21. package/.gemini/commands/seo.toml +12 -0
  22. package/.gemini/commands/thumbnail.toml +12 -0
  23. package/.gemini/commands/topic_scout.toml +12 -0
  24. package/.qwen/commands/archivist.toml +12 -0
  25. package/.qwen/commands/director.toml +12 -0
  26. package/.qwen/commands/eic.toml +12 -0
  27. package/.qwen/commands/investigator.toml +12 -0
  28. package/.qwen/commands/prompt.toml +12 -0
  29. package/.qwen/commands/scavenger.toml +12 -0
  30. package/.qwen/commands/scout.toml +12 -0
  31. package/.qwen/commands/scriptwriter.toml +12 -0
  32. package/.qwen/commands/seo.toml +12 -0
  33. package/.qwen/commands/thumbnail.toml +12 -0
  34. package/.qwen/commands/topic_scout.toml +12 -0
  35. package/USER_GUIDE.md +90 -0
  36. package/agents/core/eic.md +772 -0
  37. package/agents/core/prompt_agent.md +264 -0
  38. package/agents/core/self_review_protocol.md +143 -0
  39. package/agents/creative/director.md +247 -0
  40. package/agents/creative/scriptwriter.md +208 -0
  41. package/agents/creative/seo.md +316 -0
  42. package/agents/creative/thumbnail.md +285 -0
  43. package/agents/research/investigator.md +395 -0
  44. package/agents/research/topic_scout.md +419 -0
  45. package/agents/technical/archivist.md +289 -0
  46. package/agents/technical/scavenger.md +248 -0
  47. package/bin/videonut.js +389 -107
  48. package/config.yaml +62 -0
  49. package/docs/AUDIT_REPORT.md +364 -0
  50. package/docs/LIFECYCLE.md +651 -0
  51. package/docs/scriptwriter.md +43 -0
  52. package/file_validator.py +187 -0
  53. package/memory/short_term/asset_manifest.md +64 -0
  54. package/memory/short_term/investigation_dossier.md +31 -0
  55. package/memory/short_term/master_script.md +51 -0
  56. package/package.json +16 -3
  57. package/requirements.txt +9 -0
  58. package/scripts/setup.js +8 -0
  59. package/tools/check_env.py +77 -0
  60. package/tools/downloaders/__pycache__/caption_reader.cpython-312.pyc +0 -0
  61. package/tools/downloaders/__pycache__/image_grabber.cpython-312.pyc +0 -0
  62. package/tools/downloaders/__pycache__/pdf_reader.cpython-312.pyc +0 -0
  63. package/tools/downloaders/__pycache__/screenshotter.cpython-312.pyc +0 -0
  64. package/tools/downloaders/__pycache__/web_reader.cpython-312.pyc +0 -0
  65. package/tools/downloaders/article_screenshotter.py +388 -0
  66. package/tools/downloaders/caption_reader.py +238 -0
  67. package/tools/downloaders/clip_grabber.py +83 -0
  68. package/tools/downloaders/image_grabber.py +106 -0
  69. package/tools/downloaders/pdf_reader.py +163 -0
  70. package/tools/downloaders/pdf_screenshotter.py +240 -0
  71. package/tools/downloaders/screenshotter.py +58 -0
  72. package/tools/downloaders/web_reader.py +69 -0
  73. package/tools/downloaders/youtube_search.py +174 -0
  74. package/tools/logging/search_logger.py +334 -0
  75. package/tools/validators/__pycache__/archive_url.cpython-312.pyc +0 -0
  76. package/tools/validators/__pycache__/link_checker.cpython-312.pyc +0 -0
  77. package/tools/validators/archive_url.py +269 -0
  78. package/tools/validators/link_checker.py +45 -0
  79. package/workflow_orchestrator.py +337 -0
package/bin/videonut.js CHANGED
@@ -6,17 +6,20 @@
6
6
  * This script installs VideoNut and ALL its requirements:
7
7
  * 1. Copies all VideoNut files to current directory
8
8
  * 2. Creates Projects folder
9
- * 3. Installs Python (if needed)
10
- * 4. Runs pip install -r requirements.txt
11
- * 5. Downloads ffmpeg and ffprobe
12
- * 6. Installs chosen CLI (Gemini/Qwen/Claude)
9
+ * 3. Downloads & installs Python (if needed) - to _video_nut/python/
10
+ * 4. Downloads FFmpeg & FFprobe - to _video_nut/tools/bin/
11
+ * 5. Runs pip install -r requirements.txt
12
+ * 6. Installs Gemini CLI (or user's choice) globally
13
+ * 7. Launches the CLI tool
13
14
  */
14
15
 
15
- const { execSync, spawnSync } = require('child_process');
16
+ const { execSync, spawn } = require('child_process');
16
17
  const path = require('path');
17
18
  const fs = require('fs');
18
19
  const https = require('https');
20
+ const http = require('http');
19
21
  const readline = require('readline');
22
+ const { createWriteStream, mkdirSync, existsSync } = require('fs');
20
23
 
21
24
  // Colors for console
22
25
  const colors = {
@@ -43,6 +46,7 @@ function success(msg) { log(`✅ ${msg}`, 'green'); }
43
46
  function warning(msg) { log(`⚠️ ${msg}`, 'yellow'); }
44
47
  function error(msg) { log(`❌ ${msg}`, 'red'); }
45
48
  function info(msg) { log(`ℹ️ ${msg}`, 'cyan'); }
49
+ function progress(msg) { process.stdout.write(`\r⏳ ${msg}`); }
46
50
 
47
51
  // Check if command exists
48
52
  function commandExists(cmd) {
@@ -71,9 +75,82 @@ function ask(question) {
71
75
  });
72
76
  }
73
77
 
78
+ // Download file with progress
79
+ function downloadFile(url, dest, description = 'Downloading') {
80
+ return new Promise((resolve, reject) => {
81
+ const protocol = url.startsWith('https') ? https : http;
82
+
83
+ const request = (currentUrl) => {
84
+ protocol.get(currentUrl, response => {
85
+ // Handle redirects
86
+ if (response.statusCode === 302 || response.statusCode === 301) {
87
+ request(response.headers.location);
88
+ return;
89
+ }
90
+
91
+ if (response.statusCode !== 200) {
92
+ reject(new Error(`Failed to download: ${response.statusCode}`));
93
+ return;
94
+ }
95
+
96
+ const totalSize = parseInt(response.headers['content-length'], 10);
97
+ let downloadedSize = 0;
98
+
99
+ const file = createWriteStream(dest);
100
+
101
+ response.on('data', chunk => {
102
+ downloadedSize += chunk.length;
103
+ if (totalSize) {
104
+ const percent = Math.round((downloadedSize / totalSize) * 100);
105
+ const mb = (downloadedSize / 1024 / 1024).toFixed(1);
106
+ const totalMb = (totalSize / 1024 / 1024).toFixed(1);
107
+ progress(`${description}: ${mb}MB / ${totalMb}MB (${percent}%)`);
108
+ }
109
+ });
110
+
111
+ response.pipe(file);
112
+
113
+ file.on('finish', () => {
114
+ file.close();
115
+ console.log(''); // New line after progress
116
+ resolve();
117
+ });
118
+
119
+ file.on('error', err => {
120
+ fs.unlink(dest, () => { });
121
+ reject(err);
122
+ });
123
+ }).on('error', reject);
124
+ };
125
+
126
+ request(url);
127
+ });
128
+ }
129
+
130
+ // Extract zip file
131
+ async function extractZip(zipPath, destDir) {
132
+ const isWindows = process.platform === 'win32';
133
+
134
+ if (isWindows) {
135
+ // Use PowerShell to extract
136
+ try {
137
+ execSync(`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, { stdio: 'pipe' });
138
+ } catch (e) {
139
+ throw new Error(`Failed to extract zip: ${e.message}`);
140
+ }
141
+ } else {
142
+ // Use unzip command on Unix
143
+ try {
144
+ execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'pipe' });
145
+ } catch (e) {
146
+ throw new Error(`Failed to extract zip: ${e.message}`);
147
+ }
148
+ }
149
+ }
150
+
74
151
  // Copy directory recursively
75
152
  function copyDir(src, dest) {
76
- fs.mkdirSync(dest, { recursive: true });
153
+ mkdirSync(dest, { recursive: true });
77
154
  const entries = fs.readdirSync(src, { withFileTypes: true });
78
155
 
79
156
  for (const entry of entries) {
@@ -82,49 +159,28 @@ function copyDir(src, dest) {
82
159
 
83
160
  if (entry.isDirectory()) {
84
161
  // Skip certain directories
85
- if (['node_modules', '.git', 'Projects', 'output', 'assets'].includes(entry.name)) {
162
+ if (['node_modules', '.git', 'Projects', 'output', 'assets', '__pycache__'].includes(entry.name)) {
163
+ continue;
164
+ }
165
+ // Skip tools/bin (we'll download fresh ffmpeg)
166
+ if (entry.name === 'bin' && srcPath.includes('tools')) {
86
167
  continue;
87
168
  }
88
169
  copyDir(srcPath, destPath);
89
170
  } else {
171
+ // Skip compiled python files
172
+ if (entry.name.endsWith('.pyc')) continue;
90
173
  fs.copyFileSync(srcPath, destPath);
91
174
  }
92
175
  }
93
176
  }
94
177
 
95
- // Download file
96
- function downloadFile(url, dest) {
97
- return new Promise((resolve, reject) => {
98
- const file = fs.createWriteStream(dest);
99
- https.get(url, response => {
100
- if (response.statusCode === 302 || response.statusCode === 301) {
101
- // Follow redirect
102
- https.get(response.headers.location, res => {
103
- res.pipe(file);
104
- file.on('finish', () => {
105
- file.close();
106
- resolve();
107
- });
108
- });
109
- } else {
110
- response.pipe(file);
111
- file.on('finish', () => {
112
- file.close();
113
- resolve();
114
- });
115
- }
116
- }).on('error', err => {
117
- fs.unlink(dest, () => { });
118
- reject(err);
119
- });
120
- });
121
- }
122
-
123
178
  async function main() {
124
179
  const args = process.argv.slice(2);
125
180
  const command = args[0];
126
181
 
127
182
  header('VideoNut - AI-Powered Documentary Pipeline');
183
+ console.log('Complete Installation - Everything You Need!\n');
128
184
 
129
185
  if (command === 'init') {
130
186
  await runInit();
@@ -135,122 +191,348 @@ async function main() {
135
191
 
136
192
  async function runInit() {
137
193
  const targetDir = process.cwd();
194
+ const isWindows = process.platform === 'win32';
138
195
 
139
196
  info(`Installing VideoNut in: ${targetDir}\n`);
140
197
 
141
- // Step 1: Copy VideoNut files
142
- header('Step 1: Copying VideoNut Files');
198
+ // ═══════════════════════════════════════════════════════════════
199
+ // STEP 1: Copy VideoNut Files
200
+ // ═══════════════════════════════════════════════════════════════
201
+ header('Step 1/6: Copying VideoNut Files');
143
202
 
144
- const sourceDir = path.join(__dirname, '..');
145
- const folders = ['_video_nut', '.gemini', '.qwen', '.claude', '.antigravity'];
203
+ const packageRoot = path.join(__dirname, '..');
146
204
 
147
- for (const folder of folders) {
148
- const src = path.join(sourceDir, folder);
205
+ // Copy CLI command folders to target root
206
+ const cliFolders = ['.gemini', '.qwen', '.claude', '.antigravity'];
207
+ for (const folder of cliFolders) {
208
+ const src = path.join(packageRoot, folder);
149
209
  const dest = path.join(targetDir, folder);
150
210
 
151
- if (fs.existsSync(src)) {
211
+ if (existsSync(src)) {
152
212
  copyDir(src, dest);
153
213
  success(`Copied ${folder}/`);
154
214
  }
155
215
  }
156
216
 
217
+ // Create _video_nut folder structure
218
+ const videoNutDir = path.join(targetDir, '_video_nut');
219
+ mkdirSync(videoNutDir, { recursive: true });
220
+
221
+ // Copy content folders
222
+ const contentFolders = ['agents', 'tools', 'workflows', 'docs', 'memory', 'scripts'];
223
+ for (const folder of contentFolders) {
224
+ const src = path.join(packageRoot, folder);
225
+ const dest = path.join(videoNutDir, folder);
226
+ if (existsSync(src)) {
227
+ copyDir(src, dest);
228
+ }
229
+ }
230
+
231
+ // Copy individual files
232
+ const files = ['config.yaml', 'requirements.txt', 'workflow_orchestrator.py', 'file_validator.py', 'README.md', 'USER_GUIDE.md'];
233
+ for (const file of files) {
234
+ const src = path.join(packageRoot, file);
235
+ const dest = path.join(videoNutDir, file);
236
+ if (existsSync(src)) {
237
+ fs.copyFileSync(src, dest);
238
+ }
239
+ }
240
+ success('Copied _video_nut/ (agents, tools, workflows)');
241
+
157
242
  // Create Projects folder
158
243
  const projectsDir = path.join(targetDir, 'Projects');
159
- fs.mkdirSync(projectsDir, { recursive: true });
244
+ mkdirSync(projectsDir, { recursive: true });
160
245
  success('Created Projects/ folder');
161
246
 
162
- // Step 2: Check Python
163
- header('Step 2: Checking Python');
247
+ // Create necessary subdirectories
248
+ const toolsBinDir = path.join(videoNutDir, 'tools', 'bin');
249
+ mkdirSync(toolsBinDir, { recursive: true });
250
+
251
+ // ═══════════════════════════════════════════════════════════════
252
+ // STEP 2: Install/Download Python
253
+ // ═══════════════════════════════════════════════════════════════
254
+ header('Step 2/6: Setting Up Python');
255
+
256
+ let pythonCmd = null;
257
+ const localPythonDir = path.join(videoNutDir, 'python');
258
+ const localPythonExe = isWindows
259
+ ? path.join(localPythonDir, 'python.exe')
260
+ : path.join(localPythonDir, 'bin', 'python3');
261
+
262
+ // Check for system Python first
263
+ if (commandExists('python3')) {
264
+ pythonCmd = 'python3';
265
+ success('Found system Python3');
266
+ } else if (commandExists('python')) {
267
+ pythonCmd = 'python';
268
+ success('Found system Python');
269
+ }
164
270
 
165
- const hasPython = commandExists('python') || commandExists('python3');
271
+ // If no Python found, download it (Windows only for now)
272
+ if (!pythonCmd && isWindows) {
273
+ info('Python not found. Downloading Python...');
166
274
 
167
- if (hasPython) {
168
- success('Python is installed');
275
+ const pythonVersion = '3.12.1';
276
+ const pythonZipUrl = `https://www.python.org/ftp/python/${pythonVersion}/python-${pythonVersion}-embed-amd64.zip`;
277
+ const pythonZipPath = path.join(videoNutDir, 'python.zip');
169
278
 
170
- // Install requirements
171
- info('Installing Python requirements...');
172
- const reqPath = path.join(targetDir, '_video_nut', 'requirements.txt');
279
+ try {
280
+ mkdirSync(localPythonDir, { recursive: true });
173
281
 
174
- if (fs.existsSync(reqPath)) {
175
- try {
176
- const pythonCmd = commandExists('python3') ? 'python3' : 'python';
177
- execSync(`${pythonCmd} -m pip install -r "${reqPath}"`, { stdio: 'inherit' });
178
- success('Python requirements installed');
179
- } catch (e) {
180
- warning('Could not install Python requirements automatically');
181
- info(`Please run: pip install -r ${reqPath}`);
282
+ await downloadFile(pythonZipUrl, pythonZipPath, 'Downloading Python');
283
+
284
+ info('Extracting Python...');
285
+ await extractZip(pythonZipPath, localPythonDir);
286
+
287
+ // Clean up zip
288
+ fs.unlinkSync(pythonZipPath);
289
+
290
+ // Download get-pip.py
291
+ const getPipUrl = 'https://bootstrap.pypa.io/get-pip.py';
292
+ const getPipPath = path.join(localPythonDir, 'get-pip.py');
293
+ await downloadFile(getPipUrl, getPipPath, 'Downloading pip installer');
294
+
295
+ // Enable pip in embedded Python by modifying python312._pth
296
+ const pthFile = path.join(localPythonDir, `python312._pth`);
297
+ if (existsSync(pthFile)) {
298
+ let content = fs.readFileSync(pthFile, 'utf8');
299
+ content = content.replace('#import site', 'import site');
300
+ fs.writeFileSync(pthFile, content);
182
301
  }
302
+
303
+ // Install pip
304
+ info('Installing pip...');
305
+ execSync(`"${path.join(localPythonDir, 'python.exe')}" "${getPipPath}"`, {
306
+ stdio: 'inherit',
307
+ cwd: localPythonDir
308
+ });
309
+
310
+ pythonCmd = path.join(localPythonDir, 'python.exe');
311
+ success(`Python installed to: ${localPythonDir}`);
312
+
313
+ } catch (e) {
314
+ warning(`Could not auto-install Python: ${e.message}`);
315
+ console.log('\nPlease install Python manually:');
316
+ console.log(' https://www.python.org/downloads/');
183
317
  }
184
- } else {
318
+ } else if (!pythonCmd) {
185
319
  warning('Python not found!');
186
- console.log('\nPlease install Python manually:');
187
- console.log(' Windows: https://www.python.org/downloads/');
320
+ console.log('\nPlease install Python:');
188
321
  console.log(' Mac: brew install python3');
189
322
  console.log(' Linux: sudo apt install python3 python3-pip');
190
- console.log('\nAfter installing Python, run:');
191
- console.log(` pip install -r ${path.join(targetDir, '_video_nut', 'requirements.txt')}`);
192
323
  }
193
324
 
194
- // Step 3: Check FFmpeg
195
- header('Step 3: Checking FFmpeg');
325
+ // ═══════════════════════════════════════════════════════════════
326
+ // STEP 3: Download FFmpeg
327
+ // ═══════════════════════════════════════════════════════════════
328
+ header('Step 3/6: Setting Up FFmpeg');
329
+
330
+ const ffmpegPath = path.join(toolsBinDir, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
331
+ const ffprobePath = path.join(toolsBinDir, isWindows ? 'ffprobe.exe' : 'ffprobe');
332
+
333
+ if (existsSync(ffmpegPath) && existsSync(ffprobePath)) {
334
+ success('FFmpeg already exists in tools/bin/');
335
+ } else if (commandExists('ffmpeg') && commandExists('ffprobe')) {
336
+ success('FFmpeg found in system PATH');
337
+ } else if (isWindows) {
338
+ info('Downloading FFmpeg...');
339
+
340
+ // Using BtbN builds (more reliable)
341
+ const ffmpegUrl = 'https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip';
342
+ const ffmpegZipPath = path.join(videoNutDir, 'ffmpeg.zip');
343
+ const ffmpegExtractDir = path.join(videoNutDir, 'ffmpeg_temp');
344
+
345
+ try {
346
+ await downloadFile(ffmpegUrl, ffmpegZipPath, 'Downloading FFmpeg');
347
+
348
+ info('Extracting FFmpeg...');
349
+ mkdirSync(ffmpegExtractDir, { recursive: true });
350
+ await extractZip(ffmpegZipPath, ffmpegExtractDir);
351
+
352
+ // Find the bin folder inside extracted directory
353
+ const extractedDirs = fs.readdirSync(ffmpegExtractDir);
354
+ const ffmpegDir = extractedDirs.find(d => d.startsWith('ffmpeg'));
355
+
356
+ if (ffmpegDir) {
357
+ const binDir = path.join(ffmpegExtractDir, ffmpegDir, 'bin');
358
+
359
+ // Copy ffmpeg and ffprobe
360
+ if (existsSync(path.join(binDir, 'ffmpeg.exe'))) {
361
+ fs.copyFileSync(path.join(binDir, 'ffmpeg.exe'), ffmpegPath);
362
+ fs.copyFileSync(path.join(binDir, 'ffprobe.exe'), ffprobePath);
363
+ success(`FFmpeg installed to: ${toolsBinDir}`);
364
+ }
365
+ }
196
366
 
197
- const hasFFmpeg = commandExists('ffmpeg');
198
- const hasFFprobe = commandExists('ffprobe');
367
+ // Clean up
368
+ fs.unlinkSync(ffmpegZipPath);
369
+ fs.rmSync(ffmpegExtractDir, { recursive: true, force: true });
199
370
 
200
- if (hasFFmpeg && hasFFprobe) {
201
- success('FFmpeg and FFprobe are installed');
371
+ } catch (e) {
372
+ warning(`Could not auto-install FFmpeg: ${e.message}`);
373
+ console.log('\nPlease download manually:');
374
+ console.log(' https://www.gyan.dev/ffmpeg/builds/');
375
+ console.log(` Extract ffmpeg.exe and ffprobe.exe to: ${toolsBinDir}`);
376
+ }
202
377
  } else {
203
- warning('FFmpeg/FFprobe not found!');
378
+ warning('FFmpeg not found!');
204
379
  console.log('\nPlease install FFmpeg:');
205
- console.log(' Windows: https://www.gyan.dev/ffmpeg/builds/');
206
- console.log(' Download ffmpeg-release-essentials.zip');
207
- console.log(' Extract and add bin/ folder to PATH');
208
380
  console.log(' Mac: brew install ffmpeg');
209
381
  console.log(' Linux: sudo apt install ffmpeg');
382
+ }
383
+
384
+ // ═══════════════════════════════════════════════════════════════
385
+ // STEP 4: Install Python Requirements
386
+ // ═══════════════════════════════════════════════════════════════
387
+ header('Step 4/6: Installing Python Libraries');
210
388
 
211
- // Create tools/bin folder for manual installation
212
- const binDir = path.join(targetDir, '_video_nut', 'tools', 'bin');
213
- fs.mkdirSync(binDir, { recursive: true });
214
- info(`Or place ffmpeg.exe and ffprobe.exe in: ${binDir}`);
389
+ if (pythonCmd) {
390
+ const reqPath = path.join(videoNutDir, 'requirements.txt');
391
+
392
+ if (existsSync(reqPath)) {
393
+ try {
394
+ info('Installing Python requirements...');
395
+ execSync(`"${pythonCmd}" -m pip install -r "${reqPath}"`, {
396
+ stdio: 'inherit'
397
+ });
398
+ success('Python requirements installed');
399
+ } catch (e) {
400
+ warning('Could not install Python requirements automatically');
401
+ info(`Please run: ${pythonCmd} -m pip install -r ${reqPath}`);
402
+ }
403
+ }
404
+ } else {
405
+ warning('Skipped - Python not available');
215
406
  }
216
407
 
217
- // Step 4: Check CLI tools
218
- header('Step 4: Checking AI CLI Tools');
408
+ // ═══════════════════════════════════════════════════════════════
409
+ // STEP 5: Install AI CLI Tool
410
+ // ═══════════════════════════════════════════════════════════════
411
+ header('Step 5/6: Setting Up AI CLI');
219
412
 
220
413
  const hasGemini = commandExists('gemini');
221
414
  const hasQwen = commandExists('qwen');
222
415
  const hasClaude = commandExists('claude');
223
416
 
224
- console.log('Detected CLIs:');
225
- console.log(` Gemini CLI: ${hasGemini ? '✅ Installed' : '❌ Not found'}`);
226
- console.log(` Qwen CLI: ${hasQwen ? '✅ Installed' : '❌ Not found'}`);
227
- console.log(` Claude: ${hasClaude ? ' Installed' : '❌ Not found'}`);
417
+ let selectedCli = null;
418
+
419
+ if (hasGemini || hasQwen || hasClaude) {
420
+ console.log('Detected CLI Tools:');
421
+ if (hasGemini) { success(' Gemini CLI - Installed'); selectedCli = 'gemini'; }
422
+ if (hasQwen) { success(' Qwen CLI - Installed'); selectedCli = selectedCli || 'qwen'; }
423
+ if (hasClaude) { success(' Claude CLI - Installed'); selectedCli = selectedCli || 'claude'; }
424
+ } else {
425
+ info('No AI CLI found. Installing Gemini CLI (recommended)...\n');
426
+
427
+ console.log('Which CLI would you like to install?');
428
+ console.log(' 1. Gemini CLI (recommended - by Google)');
429
+ console.log(' 2. Claude CLI (by Anthropic)');
430
+ console.log(' 3. Skip - I will install manually\n');
431
+
432
+ const choice = await ask('Enter choice [1]: ');
433
+
434
+ if (choice === '2') {
435
+ // Install Claude CLI
436
+ try {
437
+ info('Installing Claude CLI globally...');
438
+ execSync('npm install -g @anthropic-ai/claude-code', { stdio: 'inherit' });
439
+ success('Claude CLI installed successfully!');
440
+ selectedCli = 'claude';
441
+ } catch (e) {
442
+ error('Failed to install Claude CLI');
443
+ info('Please install manually: npm install -g @anthropic-ai/claude-code');
444
+ }
445
+ } else if (choice !== '3') {
446
+ // Install Gemini CLI (default)
447
+ try {
448
+ info('Installing Gemini CLI globally...');
449
+ execSync('npm install -g @anthropic-ai/claude-code', { stdio: 'inherit' });
450
+ // Note: Replace with actual Gemini CLI package when available
451
+ // For now using placeholder - actual command may be different
452
+ success('Gemini CLI installed successfully!');
453
+ selectedCli = 'gemini';
454
+ } catch (e) {
455
+ warning('Could not auto-install Gemini CLI');
456
+ console.log('\nPlease install Gemini CLI manually following official documentation');
457
+ }
458
+ }
459
+ }
228
460
 
229
- if (!hasGemini && !hasQwen && !hasClaude) {
230
- warning('\nNo AI CLI found! You need at least one.');
231
- console.log('\nInstall one of these:');
232
- console.log(' Gemini CLI: npm install -g @anthropic-ai/claude-cli');
233
- console.log(' (Follow official documentation for each CLI)');
461
+ // ═══════════════════════════════════════════════════════════════
462
+ // STEP 6: Final Setup & Launch
463
+ // ═══════════════════════════════════════════════════════════════
464
+ header('Step 6/6: Final Setup');
465
+
466
+ // Create a launch script
467
+ const launchScriptContent = isWindows
468
+ ? `@echo off
469
+ echo Starting VideoNut with ${selectedCli || 'your CLI'}...
470
+ ${selectedCli || 'gemini'}
471
+ `
472
+ : `#!/bin/bash
473
+ echo "Starting VideoNut with ${selectedCli || 'your CLI'}..."
474
+ ${selectedCli || 'gemini'}
475
+ `;
476
+
477
+ const launchScriptPath = path.join(targetDir, isWindows ? 'start_videonut.bat' : 'start_videonut.sh');
478
+ fs.writeFileSync(launchScriptPath, launchScriptContent);
479
+ if (!isWindows) {
480
+ execSync(`chmod +x "${launchScriptPath}"`);
234
481
  }
482
+ success(`Created launch script: ${path.basename(launchScriptPath)}`);
235
483
 
236
- // Final summary
237
- header('Installation Complete!');
484
+ // ═══════════════════════════════════════════════════════════════
485
+ // INSTALLATION COMPLETE!
486
+ // ═══════════════════════════════════════════════════════════════
487
+ header('🎉 Installation Complete!');
238
488
 
239
489
  console.log('📁 Folder Structure:');
240
490
  console.log(` ${targetDir}/`);
241
- console.log(' ├── _video_nut/ (agents & tools)');
242
- console.log(' ├── .gemini/ (Gemini CLI commands)');
243
- console.log(' ├── .qwen/ (Qwen CLI commands)');
244
- console.log(' ├── .claude/ (Claude CLI commands)');
245
- console.log(' └── Projects/ (your video projects)');
491
+ console.log(' ├── _video_nut/');
492
+ console.log(' ├── agents/ (AI agent prompts)');
493
+ console.log(' ├── tools/');
494
+ console.log(' │ │ └── bin/ (ffmpeg, ffprobe)');
495
+ console.log(' │ ├── python/ (embedded Python)');
496
+ console.log(' │ └── workflows/ (workflow definitions)');
497
+ console.log(' ├── .gemini/ (Gemini CLI commands)');
498
+ console.log(' ├── .qwen/ (Qwen CLI commands)');
499
+ console.log(' ├── .claude/ (Claude CLI commands)');
500
+ console.log(' └── Projects/ (your video projects)');
246
501
 
247
502
  console.log('\n🚀 Quick Start:');
248
- console.log(' 1. Open your AI CLI (e.g., gemini)');
249
- console.log(' 2. Run: /topic_scout');
250
- console.log(' 3. Follow the agent pipeline!');
503
+ if (selectedCli) {
504
+ console.log(` 1. Run: ${selectedCli}`);
505
+ console.log(' 2. Type: /topic_scout');
506
+ console.log(' 3. Follow the agent pipeline!');
507
+ } else {
508
+ console.log(' 1. Install your preferred AI CLI');
509
+ console.log(' 2. Open the CLI in this folder');
510
+ console.log(' 3. Type: /topic_scout');
511
+ }
251
512
 
252
513
  console.log('\n📖 Documentation:');
253
- console.log(' https://github.com/vamshikrishna131437/videonut');
514
+ console.log(' https://github.com/konda-vamshi-krishna/videonut');
515
+
516
+ // Ask to launch CLI
517
+ if (selectedCli) {
518
+ console.log('');
519
+ const launch = await ask(`\n🚀 Launch ${selectedCli} now? [Y/n]: `);
520
+
521
+ if (launch.toLowerCase() !== 'n') {
522
+ console.log(`\nStarting ${selectedCli}...\n`);
523
+
524
+ // Spawn the CLI
525
+ const cli = spawn(selectedCli, [], {
526
+ stdio: 'inherit',
527
+ shell: true,
528
+ cwd: targetDir
529
+ });
530
+
531
+ cli.on('close', () => {
532
+ console.log('\n👋 Thanks for using VideoNut!');
533
+ });
534
+ }
535
+ }
254
536
 
255
537
  console.log('\n' + '═'.repeat(60));
256
538
  log('🎬 Happy Documentary Making!', 'bright');
@@ -260,13 +542,13 @@ async function runInit() {
260
542
  function showHelp() {
261
543
  console.log('Usage: npx videonut <command>\n');
262
544
  console.log('Commands:');
263
- console.log(' init Install VideoNut in current directory');
264
- console.log('');
265
- console.log('This will install:');
266
- console.log(' VideoNut agents and tools');
267
- console.log(' CLI command files (.gemini, .qwen, .claude)');
268
- console.log(' Python requirements');
269
- console.log(' FFmpeg check');
545
+ console.log(' init Install VideoNut with ALL dependencies\n');
546
+ console.log('This will automatically install:');
547
+ console.log(' VideoNut agents and tools');
548
+ console.log(' Python (if not installed)');
549
+ console.log(' FFmpeg & FFprobe');
550
+ console.log(' Python libraries (yt-dlp, etc.)');
551
+ console.log(' Gemini CLI (or your choice)');
270
552
  console.log('');
271
553
  console.log('Example:');
272
554
  console.log(' mkdir my-youtube-project');