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.
- package/.antigravity/config.toml +8 -0
- package/.claude/commands/archivist.toml +12 -0
- package/.claude/commands/director.toml +12 -0
- package/.claude/commands/eic.toml +12 -0
- package/.claude/commands/investigator.toml +12 -0
- package/.claude/commands/prompt.toml +12 -0
- package/.claude/commands/scavenger.toml +12 -0
- package/.claude/commands/scout.toml +12 -0
- package/.claude/commands/scriptwriter.toml +12 -0
- package/.claude/commands/seo.toml +12 -0
- package/.claude/commands/thumbnail.toml +12 -0
- package/.claude/commands/topic_scout.toml +12 -0
- package/.gemini/commands/archivist.toml +12 -0
- package/.gemini/commands/director.toml +12 -0
- package/.gemini/commands/eic.toml +12 -0
- package/.gemini/commands/investigator.toml +12 -0
- package/.gemini/commands/prompt.toml +12 -0
- package/.gemini/commands/scavenger.toml +12 -0
- package/.gemini/commands/scout.toml +12 -0
- package/.gemini/commands/scriptwriter.toml +12 -0
- package/.gemini/commands/seo.toml +12 -0
- package/.gemini/commands/thumbnail.toml +12 -0
- package/.gemini/commands/topic_scout.toml +12 -0
- package/.qwen/commands/archivist.toml +12 -0
- package/.qwen/commands/director.toml +12 -0
- package/.qwen/commands/eic.toml +12 -0
- package/.qwen/commands/investigator.toml +12 -0
- package/.qwen/commands/prompt.toml +12 -0
- package/.qwen/commands/scavenger.toml +12 -0
- package/.qwen/commands/scout.toml +12 -0
- package/.qwen/commands/scriptwriter.toml +12 -0
- package/.qwen/commands/seo.toml +12 -0
- package/.qwen/commands/thumbnail.toml +12 -0
- package/.qwen/commands/topic_scout.toml +12 -0
- package/USER_GUIDE.md +90 -0
- package/agents/core/eic.md +772 -0
- package/agents/core/prompt_agent.md +264 -0
- package/agents/core/self_review_protocol.md +143 -0
- package/agents/creative/director.md +247 -0
- package/agents/creative/scriptwriter.md +208 -0
- package/agents/creative/seo.md +316 -0
- package/agents/creative/thumbnail.md +285 -0
- package/agents/research/investigator.md +395 -0
- package/agents/research/topic_scout.md +419 -0
- package/agents/technical/archivist.md +289 -0
- package/agents/technical/scavenger.md +248 -0
- package/bin/videonut.js +389 -107
- package/config.yaml +62 -0
- package/docs/AUDIT_REPORT.md +364 -0
- package/docs/LIFECYCLE.md +651 -0
- package/docs/scriptwriter.md +43 -0
- package/file_validator.py +187 -0
- package/memory/short_term/asset_manifest.md +64 -0
- package/memory/short_term/investigation_dossier.md +31 -0
- package/memory/short_term/master_script.md +51 -0
- package/package.json +16 -3
- package/requirements.txt +9 -0
- package/scripts/setup.js +8 -0
- package/tools/check_env.py +77 -0
- package/tools/downloaders/__pycache__/caption_reader.cpython-312.pyc +0 -0
- package/tools/downloaders/__pycache__/image_grabber.cpython-312.pyc +0 -0
- package/tools/downloaders/__pycache__/pdf_reader.cpython-312.pyc +0 -0
- package/tools/downloaders/__pycache__/screenshotter.cpython-312.pyc +0 -0
- package/tools/downloaders/__pycache__/web_reader.cpython-312.pyc +0 -0
- package/tools/downloaders/article_screenshotter.py +388 -0
- package/tools/downloaders/caption_reader.py +238 -0
- package/tools/downloaders/clip_grabber.py +83 -0
- package/tools/downloaders/image_grabber.py +106 -0
- package/tools/downloaders/pdf_reader.py +163 -0
- package/tools/downloaders/pdf_screenshotter.py +240 -0
- package/tools/downloaders/screenshotter.py +58 -0
- package/tools/downloaders/web_reader.py +69 -0
- package/tools/downloaders/youtube_search.py +174 -0
- package/tools/logging/search_logger.py +334 -0
- package/tools/validators/__pycache__/archive_url.cpython-312.pyc +0 -0
- package/tools/validators/__pycache__/link_checker.cpython-312.pyc +0 -0
- package/tools/validators/archive_url.py +269 -0
- package/tools/validators/link_checker.py +45 -0
- 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.
|
|
10
|
-
* 4.
|
|
11
|
-
* 5.
|
|
12
|
-
* 6. Installs
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
142
|
-
|
|
198
|
+
// ═══════════════════════════════════════════════════════════════
|
|
199
|
+
// STEP 1: Copy VideoNut Files
|
|
200
|
+
// ═══════════════════════════════════════════════════════════════
|
|
201
|
+
header('Step 1/6: Copying VideoNut Files');
|
|
143
202
|
|
|
144
|
-
const
|
|
145
|
-
const folders = ['_video_nut', '.gemini', '.qwen', '.claude', '.antigravity'];
|
|
203
|
+
const packageRoot = path.join(__dirname, '..');
|
|
146
204
|
|
|
147
|
-
|
|
148
|
-
|
|
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 (
|
|
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
|
-
|
|
244
|
+
mkdirSync(projectsDir, { recursive: true });
|
|
160
245
|
success('Created Projects/ folder');
|
|
161
246
|
|
|
162
|
-
//
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
const reqPath = path.join(targetDir, '_video_nut', 'requirements.txt');
|
|
279
|
+
try {
|
|
280
|
+
mkdirSync(localPythonDir, { recursive: true });
|
|
173
281
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
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
|
-
//
|
|
195
|
-
|
|
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
|
-
|
|
198
|
-
|
|
367
|
+
// Clean up
|
|
368
|
+
fs.unlinkSync(ffmpegZipPath);
|
|
369
|
+
fs.rmSync(ffmpegExtractDir, { recursive: true, force: true });
|
|
199
370
|
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
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
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
//
|
|
218
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
//
|
|
237
|
-
|
|
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/
|
|
242
|
-
console.log(' ├──
|
|
243
|
-
console.log(' ├──
|
|
244
|
-
console.log('
|
|
245
|
-
console.log('
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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/
|
|
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
|
|
264
|
-
console.log('');
|
|
265
|
-
console.log('
|
|
266
|
-
console.log('
|
|
267
|
-
console.log('
|
|
268
|
-
console.log('
|
|
269
|
-
console.log('
|
|
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');
|