videonut 1.2.7 → 1.3.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 (60) hide show
  1. package/README.md +272 -272
  2. package/USER_GUIDE.md +90 -90
  3. package/agents/core/eic.md +771 -771
  4. package/agents/creative/director.md +246 -246
  5. package/agents/creative/scriptwriter.md +207 -207
  6. package/agents/research/investigator.md +394 -394
  7. package/agents/technical/archivist.md +288 -288
  8. package/agents/technical/scavenger.md +247 -247
  9. package/bin/videonut.js +37 -21
  10. package/config.yaml +61 -61
  11. package/docs/scriptwriter.md +42 -42
  12. package/file_validator.py +186 -186
  13. package/memory/short_term/asset_manifest.md +64 -64
  14. package/memory/short_term/investigation_dossier.md +31 -31
  15. package/memory/short_term/master_script.md +51 -51
  16. package/package.json +61 -64
  17. package/requirements.txt +8 -8
  18. package/setup.js +33 -15
  19. package/tools/check_env.py +76 -76
  20. package/tools/downloaders/caption_reader.py +237 -237
  21. package/tools/downloaders/clip_grabber.py +82 -82
  22. package/tools/downloaders/image_grabber.py +105 -105
  23. package/tools/downloaders/pdf_reader.py +163 -163
  24. package/tools/downloaders/screenshotter.py +58 -58
  25. package/tools/downloaders/web_reader.py +69 -69
  26. package/tools/validators/link_checker.py +45 -45
  27. package/workflow_orchestrator.py +336 -336
  28. package/.claude/commands/archivist.toml +0 -12
  29. package/.claude/commands/director.toml +0 -12
  30. package/.claude/commands/eic.toml +0 -12
  31. package/.claude/commands/investigator.toml +0 -12
  32. package/.claude/commands/prompt.toml +0 -12
  33. package/.claude/commands/scavenger.toml +0 -12
  34. package/.claude/commands/scout.toml +0 -12
  35. package/.claude/commands/scriptwriter.toml +0 -12
  36. package/.claude/commands/seo.toml +0 -12
  37. package/.claude/commands/thumbnail.toml +0 -12
  38. package/.claude/commands/topic_scout.toml +0 -12
  39. package/.gemini/commands/archivist.toml +0 -12
  40. package/.gemini/commands/director.toml +0 -12
  41. package/.gemini/commands/eic.toml +0 -12
  42. package/.gemini/commands/investigator.toml +0 -12
  43. package/.gemini/commands/prompt.toml +0 -12
  44. package/.gemini/commands/scavenger.toml +0 -12
  45. package/.gemini/commands/scout.toml +0 -12
  46. package/.gemini/commands/scriptwriter.toml +0 -12
  47. package/.gemini/commands/seo.toml +0 -12
  48. package/.gemini/commands/thumbnail.toml +0 -12
  49. package/.gemini/commands/topic_scout.toml +0 -12
  50. package/.qwen/commands/archivist.toml +0 -12
  51. package/.qwen/commands/director.toml +0 -12
  52. package/.qwen/commands/eic.toml +0 -12
  53. package/.qwen/commands/investigator.toml +0 -12
  54. package/.qwen/commands/prompt.toml +0 -12
  55. package/.qwen/commands/scavenger.toml +0 -12
  56. package/.qwen/commands/scout.toml +0 -12
  57. package/.qwen/commands/scriptwriter.toml +0 -12
  58. package/.qwen/commands/seo.toml +0 -12
  59. package/.qwen/commands/thumbnail.toml +0 -12
  60. package/.qwen/commands/topic_scout.toml +0 -12
package/config.yaml CHANGED
@@ -1,62 +1,62 @@
1
- # VideoNut Configuration
2
- # This file is managed by Topic Scout agent. All other agents READ ONLY.
3
-
4
- # ═══════════════════════════════════════════════════════════════════
5
- # USER SETTINGS
6
- # ═══════════════════════════════════════════════════════════════════
7
- user_name: "Producer"
8
- communication_language: "Telugu"
9
-
10
- # ═══════════════════════════════════════════════════════════════════
11
- # PROJECT SETTINGS (Set by Topic Scout)
12
- # ═══════════════════════════════════════════════════════════════════
13
- projects_folder: "./Projects"
14
- current_project: "gemini_2025-12-30_SEBI-Hindenburg_004"
15
-
16
- # ═══════════════════════════════════════════════════════════════════
17
- # VIDEO PRODUCTION SETTINGS (Set by Topic Scout)
18
- # ═══════════════════════════════════════════════════════════════════
19
- video_format: "Investigative Documentary"
20
- target_duration: 20
21
- target_line_count: 2700
22
- audio_language: "Telugu"
23
-
24
- # ═══════════════════════════════════════════════════════════════════
25
- # SCOPE & REGION (Set by Topic Scout - User Selected)
26
- # ═══════════════════════════════════════════════════════════════════
27
- # scope: international | national | regional
28
- scope: "national"
29
-
30
- # country: Only set if scope is "national" (e.g., India, USA, UK)
31
- country: "India"
32
-
33
- # region: User selected region (NOT auto-derived from language)
34
- # Examples: "Telangana", "Andhra Pradesh", "Maharashtra", "Tamil Nadu", "Pan-India"
35
- region: "Pan-India"
36
-
37
- # ═══════════════════════════════════════════════════════════════════
38
- # INDUSTRY TAG (Set by Topic Scout)
39
- # ═══════════════════════════════════════════════════════════════════
40
- # Helps agents stay in context and use appropriate sources
41
- # Options: Finance, Stock Market, Political, Crime, Social Awareness,
42
- # Technology, Entertainment, Sports, Health, Environment, Business, Other
43
- industry_tag: "Political"
44
-
45
- # Industry-specific sources (auto-populated based on industry_tag)
46
- # Finance: RBI, SEBI, Economic Times, Mint
47
- # Stock Market: NSE, BSE, MoneyControl, TradingView
48
- # Political: Election Commission, PRS Legislative, Parliament videos
49
- # Crime: Court records, Police statements, NCRB data
50
- # Social Awareness: NGO reports, Government schemes, RTI data
51
-
52
- # ═══════════════════════════════════════════════════════════════════
53
- # EIC CORRECTION TRACKING (Set by EIC after review)
54
- # ═══════════════════════════════════════════════════════════════════
55
- # correction_log: Path to the correction log file (relative to project folder)
56
- # Status: pending_review | corrections_needed | approved
57
- correction_log: ""
58
- correction_status: "pending_review"
59
-
60
- # Agents with pending corrections (comma-separated)
61
- # Example: "investigator,scriptwriter,director"
1
+ # VideoNut Configuration
2
+ # This file is managed by Topic Scout agent. All other agents READ ONLY.
3
+
4
+ # ═══════════════════════════════════════════════════════════════════
5
+ # USER SETTINGS
6
+ # ═══════════════════════════════════════════════════════════════════
7
+ user_name: "Producer"
8
+ communication_language: "Telugu"
9
+
10
+ # ═══════════════════════════════════════════════════════════════════
11
+ # PROJECT SETTINGS (Set by Topic Scout)
12
+ # ═══════════════════════════════════════════════════════════════════
13
+ projects_folder: "./Projects"
14
+ current_project: "gemini_2025-12-30_SEBI-Hindenburg_004"
15
+
16
+ # ═══════════════════════════════════════════════════════════════════
17
+ # VIDEO PRODUCTION SETTINGS (Set by Topic Scout)
18
+ # ═══════════════════════════════════════════════════════════════════
19
+ video_format: "Investigative Documentary"
20
+ target_duration: 20
21
+ target_line_count: 2700
22
+ audio_language: "Telugu"
23
+
24
+ # ═══════════════════════════════════════════════════════════════════
25
+ # SCOPE & REGION (Set by Topic Scout - User Selected)
26
+ # ═══════════════════════════════════════════════════════════════════
27
+ # scope: international | national | regional
28
+ scope: "national"
29
+
30
+ # country: Only set if scope is "national" (e.g., India, USA, UK)
31
+ country: "India"
32
+
33
+ # region: User selected region (NOT auto-derived from language)
34
+ # Examples: "Telangana", "Andhra Pradesh", "Maharashtra", "Tamil Nadu", "Pan-India"
35
+ region: "Pan-India"
36
+
37
+ # ═══════════════════════════════════════════════════════════════════
38
+ # INDUSTRY TAG (Set by Topic Scout)
39
+ # ═══════════════════════════════════════════════════════════════════
40
+ # Helps agents stay in context and use appropriate sources
41
+ # Options: Finance, Stock Market, Political, Crime, Social Awareness,
42
+ # Technology, Entertainment, Sports, Health, Environment, Business, Other
43
+ industry_tag: "Political"
44
+
45
+ # Industry-specific sources (auto-populated based on industry_tag)
46
+ # Finance: RBI, SEBI, Economic Times, Mint
47
+ # Stock Market: NSE, BSE, MoneyControl, TradingView
48
+ # Political: Election Commission, PRS Legislative, Parliament videos
49
+ # Crime: Court records, Police statements, NCRB data
50
+ # Social Awareness: NGO reports, Government schemes, RTI data
51
+
52
+ # ═══════════════════════════════════════════════════════════════════
53
+ # EIC CORRECTION TRACKING (Set by EIC after review)
54
+ # ═══════════════════════════════════════════════════════════════════
55
+ # correction_log: Path to the correction log file (relative to project folder)
56
+ # Status: pending_review | corrections_needed | approved
57
+ correction_log: ""
58
+ correction_status: "pending_review"
59
+
60
+ # Agents with pending corrections (comma-separated)
61
+ # Example: "investigator,scriptwriter,director"
62
62
  agents_with_errors: ""
@@ -1,43 +1,43 @@
1
- # ✍️ Scriptwriter Agent: Sorkin
2
-
3
- The Scriptwriter is a specialized creative agent designed to bridge the gap between raw investigative data and cinematic visual direction. It focuses exclusively on the **human element**, **narrative flow**, and **viewer retention**.
4
-
5
- ## 🎯 Purpose
6
- The primary goal of the Scriptwriter is to humanize the findings of the Investigator. While the Investigator provides the "Truth," the Scriptwriter provides the "Soul." It ensures that all 360-degree perspectives (victims, authorities, systems) are represented with emotional depth and persuasive rhetoric.
7
-
8
- ## 🛠️ Setup & Dependencies
9
- The Scriptwriter operates within the VideoNut ecosystem and requires the following:
10
- - **Project Structure:** Must have access to the active project's `truth_dossier.md`.
11
- - **Dependencies:**
12
- - Python 3.8+
13
- - VideoNut Core Framework (for file I/O)
14
- - `config.yaml` with an active `current_project` path.
15
-
16
- ## 🚀 Usage
17
-
18
- ### CLI Integration
19
- You can invoke the Scriptwriter directly from the Gemini CLI:
20
- ```bash
21
- /scriptwriter
22
- ```
23
-
24
- ### Workflow Execution
25
- 1. **Load Agent:** Use `/scriptwriter`.
26
- 2. **Execute Command:** Select `[WS] Write Script`.
27
- 3. **Input:** Automatically reads the `truth_dossier.md` from the active project.
28
- 4. **Output:** Generates `narrative_script.md` in the project folder.
29
-
30
- ## 🧠 Core Logic: The 360-Degree Perspective
31
- The agent uses a **Recursive Narrative Audit** to ensure:
32
- - **The Hook:** A 45-second high-retention opening.
33
- - **Stakeholder Inclusion:** Dedicated emotional beats for victims found by the "Bloodhound" protocol.
34
- - **Rhetorical Sharpness:** Criticism of authority and systemic issues where supported by facts.
35
-
36
- ## 📝 Configuration (TOML)
37
- The agent is registered in the CLI via the following TOML structure:
38
- ```toml
39
- [scriptwriter]
40
- name = "scriptwriter"
41
- description = "Invoke the Scriptwriter agent (Sorkin)"
42
- agent_path = "_video_nut/agents/creative/scriptwriter.md"
1
+ # ✍️ Scriptwriter Agent: Sorkin
2
+
3
+ The Scriptwriter is a specialized creative agent designed to bridge the gap between raw investigative data and cinematic visual direction. It focuses exclusively on the **human element**, **narrative flow**, and **viewer retention**.
4
+
5
+ ## 🎯 Purpose
6
+ The primary goal of the Scriptwriter is to humanize the findings of the Investigator. While the Investigator provides the "Truth," the Scriptwriter provides the "Soul." It ensures that all 360-degree perspectives (victims, authorities, systems) are represented with emotional depth and persuasive rhetoric.
7
+
8
+ ## 🛠️ Setup & Dependencies
9
+ The Scriptwriter operates within the VideoNut ecosystem and requires the following:
10
+ - **Project Structure:** Must have access to the active project's `truth_dossier.md`.
11
+ - **Dependencies:**
12
+ - Python 3.8+
13
+ - VideoNut Core Framework (for file I/O)
14
+ - `config.yaml` with an active `current_project` path.
15
+
16
+ ## 🚀 Usage
17
+
18
+ ### CLI Integration
19
+ You can invoke the Scriptwriter directly from the Gemini CLI:
20
+ ```bash
21
+ /scriptwriter
22
+ ```
23
+
24
+ ### Workflow Execution
25
+ 1. **Load Agent:** Use `/scriptwriter`.
26
+ 2. **Execute Command:** Select `[WS] Write Script`.
27
+ 3. **Input:** Automatically reads the `truth_dossier.md` from the active project.
28
+ 4. **Output:** Generates `narrative_script.md` in the project folder.
29
+
30
+ ## 🧠 Core Logic: The 360-Degree Perspective
31
+ The agent uses a **Recursive Narrative Audit** to ensure:
32
+ - **The Hook:** A 45-second high-retention opening.
33
+ - **Stakeholder Inclusion:** Dedicated emotional beats for victims found by the "Bloodhound" protocol.
34
+ - **Rhetorical Sharpness:** Criticism of authority and systemic issues where supported by facts.
35
+
36
+ ## 📝 Configuration (TOML)
37
+ The agent is registered in the CLI via the following TOML structure:
38
+ ```toml
39
+ [scriptwriter]
40
+ name = "scriptwriter"
41
+ description = "Invoke the Scriptwriter agent (Sorkin)"
42
+ agent_path = "_video_nut/agents/creative/scriptwriter.md"
43
43
  ```
package/file_validator.py CHANGED
@@ -1,187 +1,187 @@
1
- #!/usr/bin/env python3
2
- """
3
- VideoNut File Validator
4
- Validates the integrity and format of files generated by the VideoNut agents
5
- """
6
- import os
7
- import sys
8
- import re
9
- from pathlib import Path
10
-
11
- def validate_truth_dossier(file_path):
12
- """Validate the truth_dossier.md file"""
13
- if not os.path.exists(file_path):
14
- return False, f"File does not exist: {file_path}"
15
-
16
- with open(file_path, 'r', encoding='utf-8') as f:
17
- content = f.read()
18
-
19
- # Check for essential sections
20
- required_sections = ['The Investigation Blueprint', 'The Findings', 'The Angle', 'The Conflict']
21
- missing_sections = []
22
-
23
- for section in required_sections:
24
- if f'# {section}' not in content and f'## {section}' not in content:
25
- missing_sections.append(section)
26
-
27
- if missing_sections:
28
- return False, f"Missing required sections: {missing_sections}"
29
-
30
- # Check for minimum content length
31
- if len(content.strip()) < 100:
32
- return False, "File content too short, may be incomplete"
33
-
34
- return True, "Truth Dossier validation passed"
35
-
36
- def validate_narrative_script(file_path):
37
- """Validate the narrative_script.md file"""
38
- if not os.path.exists(file_path):
39
- return False, f"File does not exist: {file_path}"
40
-
41
- with open(file_path, 'r', encoding='utf-8') as f:
42
- content = f.read()
43
-
44
- # Check for essential narrative elements
45
- required_elements = ['The Hook', 'The Bridge', 'The Meat', 'The Verdict']
46
- missing_elements = []
47
-
48
- for element in required_elements:
49
- if element not in content:
50
- missing_elements.append(element)
51
-
52
- if missing_elements:
53
- return False, f"Missing required narrative elements: {missing_elements}"
54
-
55
- # Check for minimum content length
56
- if len(content.strip()) < 100:
57
- return False, "File content too short, may be incomplete"
58
-
59
- return True, "Narrative Script validation passed"
60
-
61
- def validate_master_script(file_path):
62
- """Validate the master_script.md file"""
63
- if not os.path.exists(file_path):
64
- return False, f"File does not exist: {file_path}"
65
-
66
- with open(file_path, 'r', encoding='utf-8') as f:
67
- content = f.read()
68
-
69
- # Look for the expected format: [NARRATION: "..."] [VISUAL: Description. [Source: URL or MANUAL]]
70
- narration_pattern = r'\[NARRATION:.*?\]'
71
- visual_pattern = r'\[VISUAL:.*?\]'
72
-
73
- narration_matches = re.findall(narration_pattern, content)
74
- visual_matches = re.findall(visual_pattern, content)
75
-
76
- if len(narration_matches) == 0:
77
- return False, "No narration blocks found in expected format [NARRATION: \"...\"]"
78
-
79
- if len(visual_matches) == 0:
80
- return False, "No visual blocks found in expected format [VISUAL: ...]"
81
-
82
- if len(content.strip()) < 100:
83
- return False, "File content too short, may be incomplete"
84
-
85
- return True, f"Master Script validation passed ({len(narration_matches)} narration blocks, {len(visual_matches)} visual blocks)"
86
-
87
- def validate_asset_manifest(file_path):
88
- """Validate the asset_manifest.md file"""
89
- if not os.path.exists(file_path):
90
- return False, f"File does not exist: {file_path}"
91
-
92
- with open(file_path, 'r', encoding='utf-8') as f:
93
- content = f.read()
94
-
95
- # Look for asset entries (should contain URLs or file references)
96
- url_pattern = r'https?://[^\s<>"{}|\\^`\[\]]+'
97
- asset_pattern = r'(\[.*?\])|(\*\*.*?\*\*)' # Markdown links or bold text
98
-
99
- url_matches = re.findall(url_pattern, content)
100
- asset_matches = re.findall(asset_pattern, content)
101
-
102
- if len(url_matches) == 0 and len(asset_matches) == 0:
103
- return False, "No assets or URLs found in manifest"
104
-
105
- if len(content.strip()) < 10:
106
- return False, "File content too short, may be incomplete"
107
-
108
- return True, f"Asset Manifest validation passed ({len(url_matches)} URLs found)"
109
-
110
- def validate_project_directory(project_dir):
111
- """Validate the entire project directory structure"""
112
- results = {}
113
-
114
- # Define expected files
115
- expected_files = {
116
- 'truth_dossier.md': validate_truth_dossier,
117
- 'narrative_script.md': validate_narrative_script,
118
- 'master_script.md': validate_master_script,
119
- 'asset_manifest.md': validate_asset_manifest
120
- }
121
-
122
- for filename, validator in expected_files.items():
123
- file_path = os.path.join(project_dir, filename)
124
- is_valid, message = validator(file_path)
125
- results[filename] = (is_valid, message)
126
-
127
- return results
128
-
129
- def main():
130
- if len(sys.argv) < 2:
131
- print("Usage: python file_validator.py <project_directory> [specific_file]")
132
- print("Example: python file_validator.py ./Projects/my_project")
133
- print("Example: python file_validator.py ./Projects/my_project truth_dossier.md")
134
- sys.exit(1)
135
-
136
- project_dir = sys.argv[1]
137
- specific_file = sys.argv[2] if len(sys.argv) > 2 else None
138
-
139
- if not os.path.exists(project_dir):
140
- print(f"❌ Project directory does not exist: {project_dir}")
141
- sys.exit(1)
142
-
143
- print(f"🔍 Validating VideoNut project: {project_dir}")
144
- print("-" * 50)
145
-
146
- if specific_file:
147
- # Validate specific file
148
- validators = {
149
- 'truth_dossier.md': validate_truth_dossier,
150
- 'narrative_script.md': validate_narrative_script,
151
- 'master_script.md': validate_master_script,
152
- 'asset_manifest.md': validate_asset_manifest
153
- }
154
-
155
- if specific_file in validators:
156
- file_path = os.path.join(project_dir, specific_file)
157
- is_valid, message = validators[specific_file](file_path)
158
-
159
- status = "✅" if is_valid else "❌"
160
- print(f"{status} {specific_file}: {message}")
161
-
162
- if not is_valid:
163
- sys.exit(1)
164
- else:
165
- print(f"❌ Unknown file type: {specific_file}")
166
- print("Supported files: truth_dossier.md, narrative_script.md, master_script.md, asset_manifest.md")
167
- sys.exit(1)
168
- else:
169
- # Validate entire project
170
- results = validate_project_directory(project_dir)
171
-
172
- all_valid = True
173
- for filename, (is_valid, message) in results.items():
174
- status = "✅" if is_valid else "❌"
175
- print(f"{status} {filename}: {message}")
176
- if not is_valid:
177
- all_valid = False
178
-
179
- print("-" * 50)
180
- if all_valid:
181
- print("🎉 All files validated successfully!")
182
- else:
183
- print("❌ Some files failed validation")
184
- sys.exit(1)
185
-
186
- if __name__ == "__main__":
1
+ #!/usr/bin/env python3
2
+ """
3
+ VideoNut File Validator
4
+ Validates the integrity and format of files generated by the VideoNut agents
5
+ """
6
+ import os
7
+ import sys
8
+ import re
9
+ from pathlib import Path
10
+
11
+ def validate_truth_dossier(file_path):
12
+ """Validate the truth_dossier.md file"""
13
+ if not os.path.exists(file_path):
14
+ return False, f"File does not exist: {file_path}"
15
+
16
+ with open(file_path, 'r', encoding='utf-8') as f:
17
+ content = f.read()
18
+
19
+ # Check for essential sections
20
+ required_sections = ['The Investigation Blueprint', 'The Findings', 'The Angle', 'The Conflict']
21
+ missing_sections = []
22
+
23
+ for section in required_sections:
24
+ if f'# {section}' not in content and f'## {section}' not in content:
25
+ missing_sections.append(section)
26
+
27
+ if missing_sections:
28
+ return False, f"Missing required sections: {missing_sections}"
29
+
30
+ # Check for minimum content length
31
+ if len(content.strip()) < 100:
32
+ return False, "File content too short, may be incomplete"
33
+
34
+ return True, "Truth Dossier validation passed"
35
+
36
+ def validate_narrative_script(file_path):
37
+ """Validate the narrative_script.md file"""
38
+ if not os.path.exists(file_path):
39
+ return False, f"File does not exist: {file_path}"
40
+
41
+ with open(file_path, 'r', encoding='utf-8') as f:
42
+ content = f.read()
43
+
44
+ # Check for essential narrative elements
45
+ required_elements = ['The Hook', 'The Bridge', 'The Meat', 'The Verdict']
46
+ missing_elements = []
47
+
48
+ for element in required_elements:
49
+ if element not in content:
50
+ missing_elements.append(element)
51
+
52
+ if missing_elements:
53
+ return False, f"Missing required narrative elements: {missing_elements}"
54
+
55
+ # Check for minimum content length
56
+ if len(content.strip()) < 100:
57
+ return False, "File content too short, may be incomplete"
58
+
59
+ return True, "Narrative Script validation passed"
60
+
61
+ def validate_master_script(file_path):
62
+ """Validate the master_script.md file"""
63
+ if not os.path.exists(file_path):
64
+ return False, f"File does not exist: {file_path}"
65
+
66
+ with open(file_path, 'r', encoding='utf-8') as f:
67
+ content = f.read()
68
+
69
+ # Look for the expected format: [NARRATION: "..."] [VISUAL: Description. [Source: URL or MANUAL]]
70
+ narration_pattern = r'\[NARRATION:.*?\]'
71
+ visual_pattern = r'\[VISUAL:.*?\]'
72
+
73
+ narration_matches = re.findall(narration_pattern, content)
74
+ visual_matches = re.findall(visual_pattern, content)
75
+
76
+ if len(narration_matches) == 0:
77
+ return False, "No narration blocks found in expected format [NARRATION: \"...\"]"
78
+
79
+ if len(visual_matches) == 0:
80
+ return False, "No visual blocks found in expected format [VISUAL: ...]"
81
+
82
+ if len(content.strip()) < 100:
83
+ return False, "File content too short, may be incomplete"
84
+
85
+ return True, f"Master Script validation passed ({len(narration_matches)} narration blocks, {len(visual_matches)} visual blocks)"
86
+
87
+ def validate_asset_manifest(file_path):
88
+ """Validate the asset_manifest.md file"""
89
+ if not os.path.exists(file_path):
90
+ return False, f"File does not exist: {file_path}"
91
+
92
+ with open(file_path, 'r', encoding='utf-8') as f:
93
+ content = f.read()
94
+
95
+ # Look for asset entries (should contain URLs or file references)
96
+ url_pattern = r'https?://[^\s<>"{}|\\^`\[\]]+'
97
+ asset_pattern = r'(\[.*?\])|(\*\*.*?\*\*)' # Markdown links or bold text
98
+
99
+ url_matches = re.findall(url_pattern, content)
100
+ asset_matches = re.findall(asset_pattern, content)
101
+
102
+ if len(url_matches) == 0 and len(asset_matches) == 0:
103
+ return False, "No assets or URLs found in manifest"
104
+
105
+ if len(content.strip()) < 10:
106
+ return False, "File content too short, may be incomplete"
107
+
108
+ return True, f"Asset Manifest validation passed ({len(url_matches)} URLs found)"
109
+
110
+ def validate_project_directory(project_dir):
111
+ """Validate the entire project directory structure"""
112
+ results = {}
113
+
114
+ # Define expected files
115
+ expected_files = {
116
+ 'truth_dossier.md': validate_truth_dossier,
117
+ 'narrative_script.md': validate_narrative_script,
118
+ 'master_script.md': validate_master_script,
119
+ 'asset_manifest.md': validate_asset_manifest
120
+ }
121
+
122
+ for filename, validator in expected_files.items():
123
+ file_path = os.path.join(project_dir, filename)
124
+ is_valid, message = validator(file_path)
125
+ results[filename] = (is_valid, message)
126
+
127
+ return results
128
+
129
+ def main():
130
+ if len(sys.argv) < 2:
131
+ print("Usage: python file_validator.py <project_directory> [specific_file]")
132
+ print("Example: python file_validator.py ./Projects/my_project")
133
+ print("Example: python file_validator.py ./Projects/my_project truth_dossier.md")
134
+ sys.exit(1)
135
+
136
+ project_dir = sys.argv[1]
137
+ specific_file = sys.argv[2] if len(sys.argv) > 2 else None
138
+
139
+ if not os.path.exists(project_dir):
140
+ print(f"❌ Project directory does not exist: {project_dir}")
141
+ sys.exit(1)
142
+
143
+ print(f"🔍 Validating VideoNut project: {project_dir}")
144
+ print("-" * 50)
145
+
146
+ if specific_file:
147
+ # Validate specific file
148
+ validators = {
149
+ 'truth_dossier.md': validate_truth_dossier,
150
+ 'narrative_script.md': validate_narrative_script,
151
+ 'master_script.md': validate_master_script,
152
+ 'asset_manifest.md': validate_asset_manifest
153
+ }
154
+
155
+ if specific_file in validators:
156
+ file_path = os.path.join(project_dir, specific_file)
157
+ is_valid, message = validators[specific_file](file_path)
158
+
159
+ status = "✅" if is_valid else "❌"
160
+ print(f"{status} {specific_file}: {message}")
161
+
162
+ if not is_valid:
163
+ sys.exit(1)
164
+ else:
165
+ print(f"❌ Unknown file type: {specific_file}")
166
+ print("Supported files: truth_dossier.md, narrative_script.md, master_script.md, asset_manifest.md")
167
+ sys.exit(1)
168
+ else:
169
+ # Validate entire project
170
+ results = validate_project_directory(project_dir)
171
+
172
+ all_valid = True
173
+ for filename, (is_valid, message) in results.items():
174
+ status = "✅" if is_valid else "❌"
175
+ print(f"{status} {filename}: {message}")
176
+ if not is_valid:
177
+ all_valid = False
178
+
179
+ print("-" * 50)
180
+ if all_valid:
181
+ print("🎉 All files validated successfully!")
182
+ else:
183
+ print("❌ Some files failed validation")
184
+ sys.exit(1)
185
+
186
+ if __name__ == "__main__":
187
187
  main()