@@ -107,21 +107,61 @@ def manim(ctx: click.Context, scene: str | None) -> None:
107107@main .command ()
108108@click .option ("--tape" , default = None , help = "Render a single VHS tape." )
109109@click .option ("--strict" , is_flag = True , help = "Fail on any unexpected stderr output." )
110+ @click .option (
111+ "--timeout" ,
112+ "render_timeout_sec" ,
113+ default = None ,
114+ type = int ,
115+ help = "Override VHS per-tape timeout seconds (default from docgen.yaml vhs.render_timeout_sec)." ,
116+ )
110117@click .pass_context
111- def vhs (ctx : click .Context , tape : str | None , strict : bool ) -> None :
118+ def vhs (
119+ ctx : click .Context ,
120+ tape : str | None ,
121+ strict : bool ,
122+ render_timeout_sec : int | None ,
123+ ) -> None :
112124 """Render VHS terminal recordings."""
113125 from docgen .vhs import VHSRunner
114126
115127 cfg = ctx .obj ["config" ]
116128 runner = VHSRunner (cfg )
117- results = runner .render (tape = tape , strict = strict )
129+ results = runner .render (tape = tape , strict = strict , timeout_sec = render_timeout_sec )
118130 for r in results :
119131 status = "ok" if r .success else "FAIL"
120132 click .echo (f" [{ status } ] { r .tape } " )
121133 for e in r .errors :
122134 click .echo (f" { e } " )
123135
124136
137+ @main .command ("tape-lint" )
138+ @click .option ("--tape" , default = None , help = "Lint a single tape name or pattern." )
139+ @click .pass_context
140+ def tape_lint (ctx : click .Context , tape : str | None ) -> None :
141+ """Lint VHS tapes for potentially real/hanging commands."""
142+ from docgen .vhs import VHSRunner
143+
144+ cfg = ctx .obj ["config" ]
145+ runner = VHSRunner (cfg )
146+ reports = runner .lint_tapes (tape = tape )
147+ if not reports :
148+ click .echo ("No tape files found." )
149+ return
150+
151+ total_issues = 0
152+ for report in reports :
153+ if report .issues :
154+ click .echo (f"[WARN] { report .tape } " )
155+ for issue in report .issues :
156+ click .echo (f" - { issue } " )
157+ total_issues += 1
158+ else :
159+ click .echo (f"[ok] { report .tape } " )
160+
161+ if total_issues :
162+ raise SystemExit (1 )
163+
164+
125165@main .command ("sync-vhs" )
126166@click .option ("--segment" , default = None , help = "Sync tape(s) for one segment ID/name." )
127167@click .option ("--dry-run" , is_flag = True , help = "Preview updates without writing files." )
@@ -238,13 +278,19 @@ def pages(ctx: click.Context, force: bool) -> None:
238278@click .option ("--skip-manim" , is_flag = True )
239279@click .option ("--skip-vhs" , is_flag = True )
240280@click .option ("--skip-tape-sync" , is_flag = True , help = "Skip optional sync-vhs stage after timestamps." )
281+ @click .option (
282+ "--retry-manim" ,
283+ is_flag = True ,
284+ help = "If compose hits FREEZE GUARD, clear Manim cache and retry Manim + compose once." ,
285+ )
241286@click .pass_context
242287def generate_all (
243288 ctx : click .Context ,
244289 skip_tts : bool ,
245290 skip_manim : bool ,
246291 skip_vhs : bool ,
247292 skip_tape_sync : bool ,
293+ retry_manim : bool ,
248294) -> None :
249295 """Run full pipeline: TTS -> Manim -> VHS -> compose -> validate -> concat -> pages."""
250296 from docgen .pipeline import Pipeline
@@ -256,6 +302,7 @@ def generate_all(
256302 skip_manim = skip_manim ,
257303 skip_vhs = skip_vhs ,
258304 skip_tape_sync = skip_tape_sync ,
305+ retry_manim_on_freeze = retry_manim ,
259306 )
260307
261308
0 commit comments