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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Archive URL Tool for VideoNut
|
|
4
|
+
|
|
5
|
+
Archives web pages to Archive.is (archive.today) to preserve them before they expire.
|
|
6
|
+
News articles especially tend to disappear or go behind paywalls.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python archive_url.py --url "https://example.com/article"
|
|
10
|
+
python archive_url.py --url "https://example.com" --check-only
|
|
11
|
+
python archive_url.py --batch "urls.txt" --output "archived_urls.txt"
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import argparse
|
|
16
|
+
import time
|
|
17
|
+
import json
|
|
18
|
+
from random import uniform
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import requests
|
|
22
|
+
except ImportError:
|
|
23
|
+
print("Error: requests not installed. Install with: pip install requests")
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_existing_archive(url):
|
|
28
|
+
"""
|
|
29
|
+
Check if a URL is already archived on Archive.is.
|
|
30
|
+
Returns the archived URL if found, None otherwise.
|
|
31
|
+
"""
|
|
32
|
+
check_url = f"https://archive.is/{url}"
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
response = requests.get(check_url, timeout=10, allow_redirects=True)
|
|
36
|
+
|
|
37
|
+
# If we get redirected to an archived page, it exists
|
|
38
|
+
if response.status_code == 200 and "archive.is" in response.url:
|
|
39
|
+
return response.url
|
|
40
|
+
return None
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Error checking archive: {e}")
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def archive_url(url, wait_for_complete=True):
|
|
47
|
+
"""
|
|
48
|
+
Submit a URL to Archive.is for archiving.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
url: The URL to archive
|
|
52
|
+
wait_for_complete: Whether to wait for archiving to complete
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Dict with status and archived URL
|
|
56
|
+
"""
|
|
57
|
+
result = {
|
|
58
|
+
'success': False,
|
|
59
|
+
'original_url': url,
|
|
60
|
+
'archived_url': None,
|
|
61
|
+
'message': ''
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Rate limiting
|
|
65
|
+
delay = uniform(2, 4)
|
|
66
|
+
print(f"Rate limiting: Waiting {delay:.2f} seconds...")
|
|
67
|
+
time.sleep(delay)
|
|
68
|
+
|
|
69
|
+
# First, check if already archived
|
|
70
|
+
print(f"Checking if {url} is already archived...")
|
|
71
|
+
existing = check_existing_archive(url)
|
|
72
|
+
if existing:
|
|
73
|
+
result['success'] = True
|
|
74
|
+
result['archived_url'] = existing
|
|
75
|
+
result['message'] = "Already archived"
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
# Submit to archive.is
|
|
79
|
+
print(f"Submitting {url} to Archive.is...")
|
|
80
|
+
|
|
81
|
+
archive_submit_url = "https://archive.is/submit/"
|
|
82
|
+
|
|
83
|
+
headers = {
|
|
84
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
85
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
data = {
|
|
89
|
+
'url': url,
|
|
90
|
+
'anyway': 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
response = requests.post(
|
|
95
|
+
archive_submit_url,
|
|
96
|
+
data=data,
|
|
97
|
+
headers=headers,
|
|
98
|
+
timeout=60,
|
|
99
|
+
allow_redirects=True
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Check for the archived URL in response
|
|
103
|
+
if response.status_code == 200:
|
|
104
|
+
# The response URL should be the archived page
|
|
105
|
+
if "archive.is" in response.url or "archive.today" in response.url:
|
|
106
|
+
result['success'] = True
|
|
107
|
+
result['archived_url'] = response.url
|
|
108
|
+
result['message'] = "Successfully archived"
|
|
109
|
+
else:
|
|
110
|
+
# Sometimes archive.is returns a page with the archive link
|
|
111
|
+
# Try to extract it from the page content
|
|
112
|
+
if 'id="SHARE_LONGLINK"' in response.text:
|
|
113
|
+
# Parse out the archive URL
|
|
114
|
+
import re
|
|
115
|
+
match = re.search(r'value="(https://archive\.[^"]+)"', response.text)
|
|
116
|
+
if match:
|
|
117
|
+
result['success'] = True
|
|
118
|
+
result['archived_url'] = match.group(1)
|
|
119
|
+
result['message'] = "Successfully archived"
|
|
120
|
+
else:
|
|
121
|
+
result['message'] = "Archived but couldn't extract URL"
|
|
122
|
+
else:
|
|
123
|
+
result['message'] = "Submission sent - check archive.is manually"
|
|
124
|
+
else:
|
|
125
|
+
result['message'] = f"HTTP {response.status_code}"
|
|
126
|
+
|
|
127
|
+
except requests.exceptions.Timeout:
|
|
128
|
+
result['message'] = "Request timed out - archive.is may be slow"
|
|
129
|
+
except Exception as e:
|
|
130
|
+
result['message'] = f"Error: {str(e)}"
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def archive_batch(urls, output_file=None):
|
|
136
|
+
"""
|
|
137
|
+
Archive multiple URLs.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
urls: List of URLs to archive
|
|
141
|
+
output_file: Optional file to save results
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List of results
|
|
145
|
+
"""
|
|
146
|
+
results = []
|
|
147
|
+
total = len(urls)
|
|
148
|
+
|
|
149
|
+
for i, url in enumerate(urls, 1):
|
|
150
|
+
url = url.strip()
|
|
151
|
+
if not url or url.startswith('#'):
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
print(f"\n[{i}/{total}] Processing: {url}")
|
|
155
|
+
result = archive_url(url)
|
|
156
|
+
results.append(result)
|
|
157
|
+
|
|
158
|
+
if result['success']:
|
|
159
|
+
print(f"✅ Archived: {result['archived_url']}")
|
|
160
|
+
else:
|
|
161
|
+
print(f"❌ Failed: {result['message']}")
|
|
162
|
+
|
|
163
|
+
# Save results if output file specified
|
|
164
|
+
if output_file:
|
|
165
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
166
|
+
f.write("# Archived URLs\n")
|
|
167
|
+
f.write("# Generated by archive_url.py\n\n")
|
|
168
|
+
|
|
169
|
+
for r in results:
|
|
170
|
+
if r['success']:
|
|
171
|
+
f.write(f"ORIGINAL: {r['original_url']}\n")
|
|
172
|
+
f.write(f"ARCHIVED: {r['archived_url']}\n\n")
|
|
173
|
+
else:
|
|
174
|
+
f.write(f"FAILED: {r['original_url']}\n")
|
|
175
|
+
f.write(f"REASON: {r['message']}\n\n")
|
|
176
|
+
|
|
177
|
+
print(f"\n📁 Results saved to: {output_file}")
|
|
178
|
+
|
|
179
|
+
return results
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def main():
|
|
183
|
+
parser = argparse.ArgumentParser(
|
|
184
|
+
description="Archive web pages to Archive.is for permanent preservation.",
|
|
185
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
186
|
+
epilog="""
|
|
187
|
+
Examples:
|
|
188
|
+
# Archive a single URL
|
|
189
|
+
python archive_url.py --url "https://timesofindia.com/article/123"
|
|
190
|
+
|
|
191
|
+
# Check if URL is already archived (don't create new)
|
|
192
|
+
python archive_url.py --url "https://example.com" --check-only
|
|
193
|
+
|
|
194
|
+
# Archive multiple URLs from a file
|
|
195
|
+
python archive_url.py --batch "urls.txt" --output "archived.txt"
|
|
196
|
+
|
|
197
|
+
# Output as JSON
|
|
198
|
+
python archive_url.py --url "https://example.com" --json
|
|
199
|
+
|
|
200
|
+
Why Archive?
|
|
201
|
+
- News articles often get deleted or moved
|
|
202
|
+
- Paywalls can block access later
|
|
203
|
+
- Creates permanent proof of what was published
|
|
204
|
+
- Essential for journalism and research
|
|
205
|
+
"""
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
parser.add_argument("--url", "-u", help="URL to archive")
|
|
209
|
+
parser.add_argument("--check-only", "-c", action="store_true",
|
|
210
|
+
help="Only check if archived, don't create new archive")
|
|
211
|
+
parser.add_argument("--batch", "-b", help="File containing URLs to archive (one per line)")
|
|
212
|
+
parser.add_argument("--output", "-o", help="Output file for batch results")
|
|
213
|
+
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
|
|
214
|
+
|
|
215
|
+
args = parser.parse_args()
|
|
216
|
+
|
|
217
|
+
if not args.url and not args.batch:
|
|
218
|
+
parser.print_help()
|
|
219
|
+
print("\nError: Either --url or --batch is required")
|
|
220
|
+
sys.exit(1)
|
|
221
|
+
|
|
222
|
+
if args.batch:
|
|
223
|
+
# Batch mode
|
|
224
|
+
try:
|
|
225
|
+
with open(args.batch, 'r', encoding='utf-8') as f:
|
|
226
|
+
urls = f.readlines()
|
|
227
|
+
results = archive_batch(urls, args.output)
|
|
228
|
+
|
|
229
|
+
# Summary
|
|
230
|
+
success = sum(1 for r in results if r['success'])
|
|
231
|
+
print(f"\n📊 Summary: {success}/{len(results)} successfully archived")
|
|
232
|
+
|
|
233
|
+
except FileNotFoundError:
|
|
234
|
+
print(f"Error: File not found: {args.batch}")
|
|
235
|
+
sys.exit(1)
|
|
236
|
+
|
|
237
|
+
else:
|
|
238
|
+
# Single URL mode
|
|
239
|
+
if args.check_only:
|
|
240
|
+
print(f"Checking archive status for: {args.url}")
|
|
241
|
+
archived = check_existing_archive(args.url)
|
|
242
|
+
|
|
243
|
+
if archived:
|
|
244
|
+
if args.json:
|
|
245
|
+
print(json.dumps({'archived': True, 'url': archived}))
|
|
246
|
+
else:
|
|
247
|
+
print(f"✅ Already archived: {archived}")
|
|
248
|
+
else:
|
|
249
|
+
if args.json:
|
|
250
|
+
print(json.dumps({'archived': False, 'url': None}))
|
|
251
|
+
else:
|
|
252
|
+
print("❌ Not archived yet")
|
|
253
|
+
else:
|
|
254
|
+
result = archive_url(args.url)
|
|
255
|
+
|
|
256
|
+
if args.json:
|
|
257
|
+
print(json.dumps(result))
|
|
258
|
+
else:
|
|
259
|
+
if result['success']:
|
|
260
|
+
print(f"\n✅ Successfully archived!")
|
|
261
|
+
print(f" Original: {result['original_url']}")
|
|
262
|
+
print(f" Archived: {result['archived_url']}")
|
|
263
|
+
else:
|
|
264
|
+
print(f"\n❌ Archive failed: {result['message']}")
|
|
265
|
+
print(f" Try manually at: https://archive.is/")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
main()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
from random import uniform
|
|
5
|
+
|
|
6
|
+
def check_link(url):
|
|
7
|
+
# Add random delay to implement rate limiting
|
|
8
|
+
delay = uniform(1, 3) # Random delay between 1-3 seconds
|
|
9
|
+
print(f"Rate limiting: Waiting {delay:.2f} seconds before checking {url}", file=sys.stderr)
|
|
10
|
+
time.sleep(delay)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# More realistic User-Agent to appear like a regular browser
|
|
14
|
+
headers = {
|
|
15
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
16
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
|
17
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
|
18
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
19
|
+
'Connection': 'keep-alive',
|
|
20
|
+
'Upgrade-Insecure-Requests': '1',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
response = requests.head(url, headers=headers, timeout=5, allow_redirects=True)
|
|
24
|
+
|
|
25
|
+
if response.status_code == 200:
|
|
26
|
+
return True, "OK"
|
|
27
|
+
else:
|
|
28
|
+
# Retry with GET if HEAD fails (some servers block HEAD)
|
|
29
|
+
response = requests.get(url, headers=headers, timeout=5, stream=True)
|
|
30
|
+
if response.status_code == 200:
|
|
31
|
+
return True, "OK"
|
|
32
|
+
return False, f"Status Code: {response.status_code}"
|
|
33
|
+
|
|
34
|
+
except requests.exceptions.RequestException as e:
|
|
35
|
+
return False, f"Request error: {str(e)}"
|
|
36
|
+
except Exception as e:
|
|
37
|
+
return False, f"General error: {str(e)}"
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
if len(sys.argv) > 1:
|
|
41
|
+
url = sys.argv[1]
|
|
42
|
+
success, msg = check_link(url)
|
|
43
|
+
print(f"{'VALID' if success else 'INVALID'}: {msg}")
|
|
44
|
+
else:
|
|
45
|
+
print("Usage: python link_checker.py [URL]")
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
VideoNut Workflow Orchestrator
|
|
4
|
+
Automates the agent workflow while maintaining manual control options
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import subprocess
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
import shutil
|
|
13
|
+
|
|
14
|
+
class VideoNutOrchestrator:
|
|
15
|
+
def __init__(self, project_path):
|
|
16
|
+
self.project_path = project_path
|
|
17
|
+
self.checkpoint_file = os.path.join(project_path, ".workflow_checkpoint.json")
|
|
18
|
+
self.checkpoints = self.load_checkpoints()
|
|
19
|
+
|
|
20
|
+
def load_checkpoints(self):
|
|
21
|
+
"""Load workflow progress from checkpoint file"""
|
|
22
|
+
if os.path.exists(self.checkpoint_file):
|
|
23
|
+
with open(self.checkpoint_file, 'r') as f:
|
|
24
|
+
return json.load(f)
|
|
25
|
+
return {
|
|
26
|
+
"investigation_complete": False,
|
|
27
|
+
"scriptwriting_complete": False,
|
|
28
|
+
"direction_complete": False,
|
|
29
|
+
"scavenging_complete": False,
|
|
30
|
+
"archiving_complete": False,
|
|
31
|
+
"last_step": "none"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def save_checkpoints(self):
|
|
35
|
+
"""Save workflow progress to checkpoint file"""
|
|
36
|
+
with open(self.checkpoint_file, 'w') as f:
|
|
37
|
+
json.dump(self.checkpoints, f, indent=2)
|
|
38
|
+
|
|
39
|
+
def run_command(self, cmd, description):
|
|
40
|
+
"""Execute a command with error handling"""
|
|
41
|
+
print(f"\n🚀 {description}")
|
|
42
|
+
print(f"Command: {cmd}")
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
result = subprocess.run(
|
|
46
|
+
cmd,
|
|
47
|
+
shell=True,
|
|
48
|
+
capture_output=True,
|
|
49
|
+
text=True,
|
|
50
|
+
cwd=self.project_path
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if result.returncode == 0:
|
|
54
|
+
print(f"✅ {description} completed successfully")
|
|
55
|
+
return True
|
|
56
|
+
else:
|
|
57
|
+
print(f"❌ {description} failed:")
|
|
58
|
+
print(f"STDOUT: {result.stdout}")
|
|
59
|
+
print(f"STDERR: {result.stderr}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
print(f"❌ Error running {description}: {str(e)}")
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
def check_prerequisites(self):
|
|
67
|
+
"""Check if required files exist for each step"""
|
|
68
|
+
files_needed = {
|
|
69
|
+
"investigation": [],
|
|
70
|
+
"scriptwriting": [os.path.join(self.project_path, "truth_dossier.md")],
|
|
71
|
+
"direction": [os.path.join(self.project_path, "narrative_script.md")],
|
|
72
|
+
"scavenging": [os.path.join(self.project_path, "master_script.md")],
|
|
73
|
+
"archiving": [os.path.join(self.project_path, "asset_manifest.md")]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for step, required_files in files_needed.items():
|
|
77
|
+
for file_path in required_files:
|
|
78
|
+
if not os.path.exists(file_path):
|
|
79
|
+
print(f"❌ Prerequisite missing for {step}: {file_path}")
|
|
80
|
+
return False
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
def run_investigation(self):
|
|
84
|
+
"""Run investigation phase"""
|
|
85
|
+
if self.checkpoints["investigation_complete"]:
|
|
86
|
+
print("⏭️ Investigation already completed, skipping...")
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
print("\n🔍 Starting Investigation Phase...")
|
|
90
|
+
# This would typically require user input, so we'll note this step
|
|
91
|
+
print("Please run: /investigator and complete the investigation")
|
|
92
|
+
print("When complete, mark investigation as done by creating truth_dossier.md")
|
|
93
|
+
|
|
94
|
+
# For automation, we'd need to implement the actual investigator call
|
|
95
|
+
# For now, we'll just check if the output file exists
|
|
96
|
+
dossier_path = os.path.join(self.project_path, "truth_dossier.md")
|
|
97
|
+
if os.path.exists(dossier_path):
|
|
98
|
+
self.checkpoints["investigation_complete"] = True
|
|
99
|
+
self.checkpoints["last_step"] = "investigation"
|
|
100
|
+
self.save_checkpoints()
|
|
101
|
+
print("✅ Investigation step marked as complete")
|
|
102
|
+
return True
|
|
103
|
+
else:
|
|
104
|
+
print("❌ Investigation output not found. Please complete investigation manually.")
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
def run_scriptwriting(self):
|
|
108
|
+
"""Run scriptwriting phase"""
|
|
109
|
+
if self.checkpoints["scriptwriting_complete"]:
|
|
110
|
+
print("⏭️ Scriptwriting already completed, skipping...")
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
print("\n✍️ Starting Scriptwriting Phase...")
|
|
114
|
+
|
|
115
|
+
# Check if prerequisite exists
|
|
116
|
+
dossier_path = os.path.join(self.project_path, "truth_dossier.md")
|
|
117
|
+
if not os.path.exists(dossier_path):
|
|
118
|
+
print("❌ Prerequisite file missing: truth_dossier.md")
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
# Run the scriptwriter
|
|
122
|
+
cmd = f'python -c "import sys; sys.path.append(\'..\'); from tools.downloaders.caption_reader import *; exec(open(\'agents/creative/scriptwriter.md\').read())" 2>/dev/null || echo "Scriptwriter needs manual execution"'
|
|
123
|
+
# More realistic approach - just check if output exists
|
|
124
|
+
script_path = os.path.join(self.project_path, "narrative_script.md")
|
|
125
|
+
|
|
126
|
+
if os.path.exists(script_path):
|
|
127
|
+
self.checkpoints["scriptwriting_complete"] = True
|
|
128
|
+
self.checkpoints["last_step"] = "scriptwriting"
|
|
129
|
+
self.save_checkpoints()
|
|
130
|
+
print("✅ Scriptwriting step marked as complete")
|
|
131
|
+
return True
|
|
132
|
+
else:
|
|
133
|
+
print("❌ Narrative script not found. Please run scriptwriter manually.")
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def run_direction(self):
|
|
137
|
+
"""Run direction phase"""
|
|
138
|
+
if self.checkpoints["direction_complete"]:
|
|
139
|
+
print("⏭️ Direction already completed, skipping...")
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
print("\n🎬 Starting Direction Phase...")
|
|
143
|
+
|
|
144
|
+
# Check if prerequisite exists
|
|
145
|
+
script_path = os.path.join(self.project_path, "narrative_script.md")
|
|
146
|
+
if not os.path.exists(script_path):
|
|
147
|
+
print("❌ Prerequisite file missing: narrative_script.md")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
# Check for output
|
|
151
|
+
master_script_path = os.path.join(self.project_path, "master_script.md")
|
|
152
|
+
if os.path.exists(master_script_path):
|
|
153
|
+
self.checkpoints["direction_complete"] = True
|
|
154
|
+
self.checkpoints["last_step"] = "direction"
|
|
155
|
+
self.save_checkpoints()
|
|
156
|
+
print("✅ Direction step marked as complete")
|
|
157
|
+
return True
|
|
158
|
+
else:
|
|
159
|
+
print("❌ Master script not found. Please run director manually.")
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
def run_scavenging(self):
|
|
163
|
+
"""Run scavenging phase"""
|
|
164
|
+
if self.checkpoints["scavenging_complete"]:
|
|
165
|
+
print("⏭️ Scavenging already completed, skipping...")
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
print("\n🦅 Starting Scavenging Phase...")
|
|
169
|
+
|
|
170
|
+
# Check if prerequisite exists
|
|
171
|
+
master_script_path = os.path.join(self.project_path, "master_script.md")
|
|
172
|
+
if not os.path.exists(master_script_path):
|
|
173
|
+
print("❌ Prerequisite file missing: master_script.md")
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
# Check for output
|
|
177
|
+
manifest_path = os.path.join(self.project_path, "asset_manifest.md")
|
|
178
|
+
if os.path.exists(manifest_path):
|
|
179
|
+
self.checkpoints["scavenging_complete"] = True
|
|
180
|
+
self.checkpoints["last_step"] = "scavenging"
|
|
181
|
+
self.save_checkpoints()
|
|
182
|
+
print("✅ Scavenging step marked as complete")
|
|
183
|
+
return True
|
|
184
|
+
else:
|
|
185
|
+
print("❌ Asset manifest not found. Please run scavenger manually.")
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
def run_archiving(self):
|
|
189
|
+
"""Run archiving phase"""
|
|
190
|
+
if self.checkpoints["archiving_complete"]:
|
|
191
|
+
print("⏭️ Archiving already completed, skipping...")
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
print("\n💾 Starting Archiving Phase...")
|
|
195
|
+
|
|
196
|
+
# Check if prerequisite exists
|
|
197
|
+
manifest_path = os.path.join(self.project_path, "asset_manifest.md")
|
|
198
|
+
if not os.path.exists(manifest_path):
|
|
199
|
+
print("❌ Prerequisite file missing: asset_manifest.md")
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
# Run archivist
|
|
203
|
+
cmd = f'python -c "import sys; sys.path.append(\'..\'); exec(open(\'agents/technical/archivist.md\').read())" 2>/dev/null || echo "Archivist needs manual execution"'
|
|
204
|
+
|
|
205
|
+
# Check for output directory
|
|
206
|
+
assets_path = os.path.join(self.project_path, "assets")
|
|
207
|
+
if os.path.exists(assets_path):
|
|
208
|
+
self.checkpoints["archiving_complete"] = True
|
|
209
|
+
self.checkpoints["last_step"] = "archiving"
|
|
210
|
+
self.save_checkpoints()
|
|
211
|
+
print("✅ Archiving step marked as complete")
|
|
212
|
+
return True
|
|
213
|
+
else:
|
|
214
|
+
print("❌ Assets directory not found. Please run archivist manually.")
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
def run_full_workflow(self):
|
|
218
|
+
"""Run the complete VideoNut workflow"""
|
|
219
|
+
print("🎬 Starting VideoNut Video Production Workflow")
|
|
220
|
+
print(f"Project Path: {self.project_path}")
|
|
221
|
+
print(f"Last completed step: {self.checkpoints['last_step']}")
|
|
222
|
+
|
|
223
|
+
steps = [
|
|
224
|
+
("Investigation", self.run_investigation),
|
|
225
|
+
("Scriptwriting", self.run_scriptwriting),
|
|
226
|
+
("Direction", self.run_direction),
|
|
227
|
+
("Scavenging", self.run_scavenging),
|
|
228
|
+
("Archiving", self.run_archiving)
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
for step_name, step_func in steps:
|
|
232
|
+
print(f"\n--- {step_name.upper()} PHASE ---")
|
|
233
|
+
if not step_func():
|
|
234
|
+
print(f"❌ {step_name} phase failed. Workflow stopped.")
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
print("\n🎉 All workflow phases completed successfully!")
|
|
238
|
+
print("Your video assets are ready for editing!")
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
def main():
|
|
242
|
+
parser = argparse.ArgumentParser(description="VideoNut Workflow Orchestrator")
|
|
243
|
+
parser.add_argument("--project", required=True, help="Path to project directory")
|
|
244
|
+
parser.add_argument("--resume", action="store_true", help="Resume from last checkpoint")
|
|
245
|
+
parser.add_argument("--status", action="store_true", help="Show current workflow status")
|
|
246
|
+
parser.add_argument("--next", action="store_true", help="Show what to do next")
|
|
247
|
+
|
|
248
|
+
args = parser.parse_args()
|
|
249
|
+
|
|
250
|
+
if not os.path.exists(args.project):
|
|
251
|
+
print(f"❌ Project directory does not exist: {args.project}")
|
|
252
|
+
sys.exit(1)
|
|
253
|
+
|
|
254
|
+
orchestrator = VideoNutOrchestrator(args.project)
|
|
255
|
+
|
|
256
|
+
# Status command - show current progress
|
|
257
|
+
if args.status:
|
|
258
|
+
print("📊 VideoNut Workflow Status")
|
|
259
|
+
print("=" * 50)
|
|
260
|
+
print(f"Project: {args.project}")
|
|
261
|
+
print(f"Last step: {orchestrator.checkpoints['last_step']}")
|
|
262
|
+
print()
|
|
263
|
+
|
|
264
|
+
status_icons = {True: "✅", False: "⏳"}
|
|
265
|
+
steps = [
|
|
266
|
+
("Investigation", "investigation_complete", "truth_dossier.md"),
|
|
267
|
+
("Scriptwriting", "scriptwriting_complete", "narrative_script.md"),
|
|
268
|
+
("Direction", "direction_complete", "master_script.md"),
|
|
269
|
+
("Scavenging", "scavenging_complete", "asset_manifest.md"),
|
|
270
|
+
("Archiving", "archiving_complete", "assets/"),
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
for step_name, checkpoint_key, output_file in steps:
|
|
274
|
+
is_complete = orchestrator.checkpoints[checkpoint_key]
|
|
275
|
+
icon = status_icons[is_complete]
|
|
276
|
+
status = "Complete" if is_complete else "Pending"
|
|
277
|
+
print(f" {icon} {step_name}: {status} → {output_file}")
|
|
278
|
+
|
|
279
|
+
print()
|
|
280
|
+
sys.exit(0)
|
|
281
|
+
|
|
282
|
+
# Next command - show what to do next
|
|
283
|
+
if args.next:
|
|
284
|
+
print("🎯 What to Do Next")
|
|
285
|
+
print("=" * 50)
|
|
286
|
+
|
|
287
|
+
next_steps = {
|
|
288
|
+
"none": ("Investigation", "/investigator", "Create truth_dossier.md with research findings"),
|
|
289
|
+
"investigation": ("Scriptwriting", "/scriptwriter", "Create narrative_script.md from the dossier"),
|
|
290
|
+
"scriptwriting": ("Direction", "/director", "Create master_script.md with visual directions"),
|
|
291
|
+
"direction": ("Scavenging", "/scavenger", "Create asset_manifest.md with URLs"),
|
|
292
|
+
"scavenging": ("Archiving", "/archivist", "Download all assets to assets/ folder"),
|
|
293
|
+
"archiving": ("Complete", None, "🎉 All done! Your video assets are ready for editing."),
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
last_step = orchestrator.checkpoints['last_step']
|
|
297
|
+
step_name, command, description = next_steps.get(last_step, next_steps["none"])
|
|
298
|
+
|
|
299
|
+
print(f"📍 Current position: After '{last_step}'")
|
|
300
|
+
print(f"👉 Next step: {step_name}")
|
|
301
|
+
if command:
|
|
302
|
+
print(f"🔧 Command: {command}")
|
|
303
|
+
print(f"📝 What to do: {description}")
|
|
304
|
+
print()
|
|
305
|
+
|
|
306
|
+
# Check for missing files
|
|
307
|
+
expected_files = {
|
|
308
|
+
"none": [],
|
|
309
|
+
"investigation": [("truth_dossier.md", "Run /investigator to create this file")],
|
|
310
|
+
"scriptwriting": [("narrative_script.md", "Run /scriptwriter to create this file")],
|
|
311
|
+
"direction": [("master_script.md", "Run /director to create this file")],
|
|
312
|
+
"scavenging": [("asset_manifest.md", "Run /scavenger to create this file")],
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
for filename, hint in expected_files.get(last_step, []):
|
|
316
|
+
filepath = os.path.join(args.project, filename)
|
|
317
|
+
if not os.path.exists(filepath):
|
|
318
|
+
print(f"⚠️ Missing: {filename}")
|
|
319
|
+
print(f" Fix: {hint}")
|
|
320
|
+
|
|
321
|
+
sys.exit(0)
|
|
322
|
+
|
|
323
|
+
if args.resume:
|
|
324
|
+
print("🔄 Resuming workflow from last checkpoint...")
|
|
325
|
+
|
|
326
|
+
success = orchestrator.run_full_workflow()
|
|
327
|
+
|
|
328
|
+
if success:
|
|
329
|
+
print(f"\n✅ Workflow completed! Checkpoint file: {orchestrator.checkpoint_file}")
|
|
330
|
+
else:
|
|
331
|
+
print(f"\n❌ Workflow failed. Checkpoint file: {orchestrator.checkpoint_file}")
|
|
332
|
+
print("\n💡 TIP: Run with --next to see what to do next")
|
|
333
|
+
print("💡 TIP: Run with --status to see full progress")
|
|
334
|
+
sys.exit(1)
|
|
335
|
+
|
|
336
|
+
if __name__ == "__main__":
|
|
337
|
+
main()
|