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
@@ -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()