ostruct-cli 0.8.29__py3-none-any.whl → 1.0.0__py3-none-any.whl

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 (49) hide show
  1. ostruct/cli/__init__.py +3 -15
  2. ostruct/cli/attachment_processor.py +455 -0
  3. ostruct/cli/attachment_template_bridge.py +973 -0
  4. ostruct/cli/cli.py +157 -33
  5. ostruct/cli/click_options.py +775 -692
  6. ostruct/cli/code_interpreter.py +195 -12
  7. ostruct/cli/commands/__init__.py +0 -3
  8. ostruct/cli/commands/run.py +289 -62
  9. ostruct/cli/config.py +23 -22
  10. ostruct/cli/constants.py +89 -0
  11. ostruct/cli/errors.py +175 -5
  12. ostruct/cli/explicit_file_processor.py +0 -15
  13. ostruct/cli/file_info.py +97 -15
  14. ostruct/cli/file_list.py +43 -1
  15. ostruct/cli/file_search.py +68 -2
  16. ostruct/cli/help_json.py +235 -0
  17. ostruct/cli/mcp_integration.py +13 -16
  18. ostruct/cli/params.py +217 -0
  19. ostruct/cli/plan_assembly.py +335 -0
  20. ostruct/cli/plan_printing.py +385 -0
  21. ostruct/cli/progress_reporting.py +8 -56
  22. ostruct/cli/quick_ref_help.py +128 -0
  23. ostruct/cli/rich_config.py +299 -0
  24. ostruct/cli/runner.py +397 -190
  25. ostruct/cli/security/__init__.py +2 -0
  26. ostruct/cli/security/allowed_checker.py +41 -0
  27. ostruct/cli/security/normalization.py +13 -9
  28. ostruct/cli/security/security_manager.py +558 -17
  29. ostruct/cli/security/types.py +15 -0
  30. ostruct/cli/template_debug.py +283 -261
  31. ostruct/cli/template_debug_help.py +233 -142
  32. ostruct/cli/template_env.py +46 -5
  33. ostruct/cli/template_filters.py +415 -8
  34. ostruct/cli/template_processor.py +240 -619
  35. ostruct/cli/template_rendering.py +49 -73
  36. ostruct/cli/template_validation.py +2 -1
  37. ostruct/cli/token_validation.py +35 -15
  38. ostruct/cli/types.py +15 -19
  39. ostruct/cli/unicode_compat.py +283 -0
  40. ostruct/cli/upload_manager.py +448 -0
  41. ostruct/cli/validators.py +255 -54
  42. {ostruct_cli-0.8.29.dist-info → ostruct_cli-1.0.0.dist-info}/METADATA +230 -127
  43. ostruct_cli-1.0.0.dist-info/RECORD +80 -0
  44. ostruct/cli/commands/quick_ref.py +0 -54
  45. ostruct/cli/template_optimizer.py +0 -478
  46. ostruct_cli-0.8.29.dist-info/RECORD +0 -71
  47. {ostruct_cli-0.8.29.dist-info → ostruct_cli-1.0.0.dist-info}/LICENSE +0 -0
  48. {ostruct_cli-0.8.29.dist-info → ostruct_cli-1.0.0.dist-info}/WHEEL +0 -0
  49. {ostruct_cli-0.8.29.dist-info → ostruct_cli-1.0.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,385 @@
1
+ """Plan printing for human-readable execution plan output.
2
+
3
+ This module renders execution plans and run summaries for human consumption
4
+ using the same data structures as JSON output per UNIFIED GUIDELINES.
5
+ """
6
+
7
+ import json
8
+ import sys
9
+ from datetime import datetime
10
+ from typing import Any, Dict, Optional, TextIO
11
+
12
+ from .unicode_compat import safe_emoji, safe_format
13
+
14
+
15
+ class PlanPrinter:
16
+ """Renders execution plans for human eyes using same dict as JSON output.
17
+
18
+ This class ensures consistent rendering logic by operating on the same
19
+ data structures produced by PlanAssembler.
20
+ """
21
+
22
+ @staticmethod
23
+ def human(plan: Dict[str, Any], file: Optional[TextIO] = None) -> None:
24
+ """Print human-readable version with colors and formatting.
25
+
26
+ Args:
27
+ plan: Plan dictionary from PlanAssembler
28
+ file: Output file (defaults to stdout)
29
+ """
30
+ if file is None:
31
+ file = sys.stdout
32
+
33
+ plan_type = plan.get("type", "unknown").replace("_", " ").title()
34
+ emoji = safe_emoji("🔍")
35
+ if emoji:
36
+ print(f"{emoji} {plan_type}\n", file=file)
37
+ else:
38
+ print(f"{plan_type}\n", file=file)
39
+
40
+ # Header with timestamp
41
+ timestamp = plan["timestamp"]
42
+ if isinstance(timestamp, str):
43
+ # Handle ISO format timestamp
44
+ try:
45
+ timestamp_dt = datetime.fromisoformat(
46
+ timestamp.replace("Z", "+00:00")
47
+ )
48
+ except ValueError:
49
+ # Fallback for other string formats
50
+ timestamp_dt = datetime.now()
51
+ else:
52
+ # Handle numeric timestamp (legacy)
53
+ timestamp_dt = datetime.fromtimestamp(timestamp)
54
+
55
+ formatted_time = timestamp_dt.strftime("%Y-%m-%d %H:%M:%S")
56
+ print(safe_format("🕐 Generated: {}", formatted_time), file=file)
57
+
58
+ # Template and schema
59
+ template = plan.get("template", {})
60
+ schema = plan.get("schema", {})
61
+
62
+ template_status = (
63
+ safe_emoji("✅", "[OK]")
64
+ if template.get("exists", False)
65
+ else safe_emoji("❌", "[ERROR]")
66
+ )
67
+ schema_status = (
68
+ safe_emoji("✅", "[OK]")
69
+ if schema.get("exists", False)
70
+ else safe_emoji("❌", "[ERROR]")
71
+ )
72
+
73
+ # Show more helpful template warning information
74
+ template_path = template.get("path", "unknown")
75
+ template_warning = template.get("warning")
76
+
77
+ if template_warning:
78
+ # Use warning indicator for configuration issues with existing files
79
+ warning_status = (
80
+ safe_emoji("⚠️", "[WARNING]")
81
+ if template.get("exists", False)
82
+ else safe_emoji("❌", "[ERROR]")
83
+ )
84
+ print(
85
+ safe_format(
86
+ "📄 Template: {} {}", warning_status, template_path
87
+ ),
88
+ file=file,
89
+ )
90
+ print(
91
+ f" Warning: {template_warning}",
92
+ file=file,
93
+ )
94
+ elif template_path == "---":
95
+ # Special case for YAML frontmatter display issue (legacy)
96
+ print(
97
+ safe_format(
98
+ "📄 Template: {} {}", template_status, template_path
99
+ ),
100
+ file=file,
101
+ )
102
+ print(
103
+ " Note: Template content shown below",
104
+ file=file,
105
+ )
106
+ else:
107
+ print(
108
+ safe_format(
109
+ "📄 Template: {} {}", template_status, template_path
110
+ ),
111
+ file=file,
112
+ )
113
+
114
+ print(
115
+ safe_format(
116
+ "📋 Schema: {} {}",
117
+ schema_status,
118
+ schema.get("path", "unknown"),
119
+ ),
120
+ file=file,
121
+ )
122
+
123
+ # Model
124
+ if "model" in plan:
125
+ print(safe_format("🤖 Model: {}", plan["model"]), file=file)
126
+
127
+ # Security
128
+ security = plan.get("security", {})
129
+ if security:
130
+ print(
131
+ safe_format(
132
+ "🔒 Security: {}", security.get("mode", "unknown")
133
+ ),
134
+ file=file,
135
+ )
136
+ allowed_paths = security.get("allowed_paths", [])
137
+ if allowed_paths:
138
+ print(
139
+ f" Allowed paths: {len(allowed_paths)} configured",
140
+ file=file,
141
+ )
142
+
143
+ # Tools
144
+ tools = plan.get("tools", {})
145
+ if tools:
146
+ enabled_tools = [
147
+ name for name, enabled in tools.items() if enabled
148
+ ]
149
+ if enabled_tools:
150
+ print(
151
+ safe_format("🛠️ Tools: {}", ", ".join(enabled_tools)),
152
+ file=file,
153
+ )
154
+
155
+ print() # Blank line before attachments
156
+
157
+ # Attachments
158
+ attachments = plan.get("attachments", [])
159
+ print(safe_format("📎 Attachments ({}):", len(attachments)), file=file)
160
+
161
+ if not attachments:
162
+ print(" (none)", file=file)
163
+ else:
164
+ for att in attachments:
165
+ exists_status = (
166
+ safe_emoji("✅", "[OK]")
167
+ if att.get("exists", False)
168
+ else safe_emoji("❌", "[ERROR]")
169
+ )
170
+ targets = ", ".join(att.get("targets", []))
171
+ path = att.get("path", "unknown")
172
+ alias = att.get("alias", "unknown")
173
+
174
+ print(
175
+ f" {exists_status} {alias} → {targets}: {path}",
176
+ file=file,
177
+ )
178
+
179
+ # Show additional details for directories
180
+ if att.get("type") == "directory":
181
+ details = []
182
+ if att.get("recursive"):
183
+ details.append("recursive")
184
+ if att.get("pattern"):
185
+ details.append(f"pattern: {att['pattern']}")
186
+ if details:
187
+ print(f" ({', '.join(details)})", file=file)
188
+
189
+ # Download validation for Code Interpreter
190
+ download_validation = plan.get("download_validation", {})
191
+ if download_validation.get("enabled"):
192
+ print(safe_format("\n📥 Download Configuration:"), file=file)
193
+ print(
194
+ f" Directory: {download_validation.get('directory', 'N/A')}",
195
+ file=file,
196
+ )
197
+
198
+ # Show writability status
199
+ if download_validation.get("writable"):
200
+ print(
201
+ safe_format(
202
+ " {} Directory writable", safe_emoji("✅", "[OK]")
203
+ ),
204
+ file=file,
205
+ )
206
+ else:
207
+ print(
208
+ safe_format(
209
+ " {} Directory not writable",
210
+ safe_emoji("❌", "[ERROR]"),
211
+ ),
212
+ file=file,
213
+ )
214
+
215
+ # Show issues if any
216
+ issues = download_validation.get("issues", [])
217
+ if issues:
218
+ print(
219
+ safe_format(
220
+ " {} Issues:", safe_emoji("⚠️", "[WARNING]")
221
+ ),
222
+ file=file,
223
+ )
224
+ for issue in issues:
225
+ print(f" - {issue}", file=file)
226
+
227
+ # Show potential conflicts
228
+ conflicts = download_validation.get("conflicts", [])
229
+ if conflicts:
230
+ print(
231
+ safe_format(
232
+ " {} Potential conflicts: {}",
233
+ safe_emoji("⚠️", "[WARNING]"),
234
+ ", ".join(conflicts),
235
+ ),
236
+ file=file,
237
+ )
238
+
239
+ # Variables
240
+ variables = plan.get("variables", {})
241
+ if variables:
242
+ print(
243
+ safe_format("\n📊 Variables ({}):", len(variables)), file=file
244
+ )
245
+ for name, value in variables.items():
246
+ # Truncate long values for readability
247
+ value_str = str(value)
248
+ if len(value_str) > 50:
249
+ value_str = value_str[:47] + "..."
250
+ print(f" {name}: {value_str}", file=file)
251
+
252
+ # Cost estimate
253
+ cost = plan.get("cost_estimate", {})
254
+ if cost and cost.get("approx_usd", 0) > 0:
255
+ estimated_note = " (estimated)" if cost.get("estimated") else ""
256
+ print(
257
+ safe_format(
258
+ "\n💰 Cost: ~${:.4f}{}",
259
+ cost.get("approx_usd", 0),
260
+ estimated_note,
261
+ ),
262
+ file=file,
263
+ )
264
+ if "tokens" in cost:
265
+ print(f" Tokens: ~{cost['tokens']:,}", file=file)
266
+
267
+ # Execution time (for run summaries)
268
+ if plan.get("type") == "run_summary":
269
+ exec_time = plan.get("execution_time")
270
+ success = plan.get("success", True)
271
+
272
+ status_icon = (
273
+ safe_emoji("✅", "[OK]")
274
+ if success
275
+ else safe_emoji("❌", "[ERROR]")
276
+ )
277
+ print(
278
+ safe_format(
279
+ "\n{} Status: {}",
280
+ status_icon,
281
+ "Success" if success else "Failed",
282
+ ),
283
+ file=file,
284
+ )
285
+
286
+ if exec_time is not None:
287
+ print(
288
+ safe_format("⏱️ Execution time: {:.2f}s", exec_time),
289
+ file=file,
290
+ )
291
+
292
+ # Show error if present
293
+ if "error" in plan:
294
+ print(
295
+ safe_format(
296
+ "{} Error: {}",
297
+ safe_emoji("❌", "[ERROR]"),
298
+ plan["error"],
299
+ ),
300
+ file=file,
301
+ )
302
+
303
+ # Show cost breakdown if present
304
+ if "cost_breakdown" in plan:
305
+ cost_breakdown = plan["cost_breakdown"]
306
+ print(
307
+ safe_format(
308
+ "💰 Final cost: ${:.4f}",
309
+ cost_breakdown.get("total", 0),
310
+ ),
311
+ file=file,
312
+ )
313
+
314
+ @staticmethod
315
+ def json(
316
+ plan: Dict[str, Any], file: Optional[TextIO] = None, indent: int = 2
317
+ ) -> None:
318
+ """Print JSON version of plan.
319
+
320
+ Args:
321
+ plan: Plan dictionary from PlanAssembler
322
+ file: Output file (defaults to stdout)
323
+ indent: JSON indentation level
324
+ """
325
+ if file is None:
326
+ file = sys.stdout
327
+
328
+ json.dump(plan, file, indent=indent, default=str)
329
+ file.write("\n")
330
+
331
+ @staticmethod
332
+ def summary_line(plan: Dict[str, Any]) -> str:
333
+ """Generate a one-line summary of the plan.
334
+
335
+ Args:
336
+ plan: Plan dictionary from PlanAssembler
337
+
338
+ Returns:
339
+ Single line summary string
340
+ """
341
+ plan_type = plan.get("type", "unknown")
342
+
343
+ if plan_type == "execution_plan":
344
+ attachments = len(plan.get("attachments", []))
345
+ variables = len(plan.get("variables", {}))
346
+ return f"Plan: {attachments} attachments, {variables} variables, model {plan.get('model', 'unknown')}"
347
+
348
+ elif plan_type == "run_summary":
349
+ success = plan.get("success", True)
350
+ exec_time = plan.get("execution_time")
351
+ status = "Success" if success else "Failed"
352
+ time_str = f" in {exec_time:.2f}s" if exec_time else ""
353
+ return f"Summary: {status}{time_str}"
354
+
355
+ else:
356
+ return f"Plan: {plan_type}"
357
+
358
+ @staticmethod
359
+ def validate_and_print(
360
+ plan: Dict[str, Any],
361
+ output_format: str = "human",
362
+ file: Optional[TextIO] = None,
363
+ ) -> None:
364
+ """Validate plan structure and print in specified format.
365
+
366
+ Args:
367
+ plan: Plan dictionary from PlanAssembler
368
+ output_format: Output format ("human" or "json")
369
+ file: Output file (defaults to stdout)
370
+
371
+ Raises:
372
+ ValueError: If plan structure is invalid or format is unsupported
373
+ """
374
+ from .plan_assembly import PlanAssembler
375
+
376
+ # Validate plan structure
377
+ PlanAssembler.validate_plan(plan)
378
+
379
+ # Print in requested format
380
+ if output_format == "human":
381
+ PlanPrinter.human(plan, file)
382
+ elif output_format == "json":
383
+ PlanPrinter.json(plan, file)
384
+ else:
385
+ raise ValueError(f"Unsupported output format: {output_format}")
@@ -47,17 +47,17 @@ class ProcessingResult:
47
47
  class EnhancedProgressReporter:
48
48
  """Enhanced progress reporter with user-friendly language and transparency."""
49
49
 
50
- def __init__(self, verbose: bool = False, progress_level: str = "basic"):
50
+ def __init__(self, verbose: bool = False, progress: str = "basic"):
51
51
  """Initialize the progress reporter.
52
52
 
53
53
  Args:
54
54
  verbose: Enable verbose logging output
55
- progress_level: Progress level (none, basic, detailed)
55
+ progress: Progress level (none, basic, detailed)
56
56
  """
57
57
  self.verbose = verbose
58
- self.progress_level = progress_level
59
- self.should_report = progress_level != "none"
60
- self.detailed = progress_level == "detailed" or verbose
58
+ self.progress = progress
59
+ self.should_report = progress != "none"
60
+ self.detailed = progress == "detailed" or verbose
61
61
 
62
62
  def report_phase(self, phase_name: str, emoji: str = "⚙️") -> None:
63
63
  """Report the start of a major processing phase.
@@ -69,27 +69,6 @@ class EnhancedProgressReporter:
69
69
  if self.should_report:
70
70
  click.echo(f"{emoji} {phase_name}...", err=True)
71
71
 
72
- def report_optimization(self, optimizations: List[str]) -> None:
73
- """Report template optimization results with user-friendly language.
74
-
75
- Args:
76
- optimizations: List of optimization transformations applied
77
- """
78
- if not self.should_report or not optimizations:
79
- return
80
-
81
- if self.detailed:
82
- click.echo("🔧 Template optimization:", err=True)
83
- for optimization in optimizations:
84
- # Convert technical language to user-friendly descriptions
85
- user_friendly = self._humanize_optimization(optimization)
86
- click.echo(f" → {user_friendly}", err=True)
87
- else:
88
- click.echo(
89
- f"🔧 Optimized template for better AI performance ({len(optimizations)} improvements)",
90
- err=True,
91
- )
92
-
93
72
  def report_file_routing(
94
73
  self,
95
74
  template_files: List[str],
@@ -322,33 +301,6 @@ class EnhancedProgressReporter:
322
301
  else:
323
302
  click.echo("✅ Validation passed", err=True)
324
303
 
325
- def _humanize_optimization(self, technical_message: str) -> str:
326
- """Convert technical optimization messages to user-friendly language.
327
-
328
- Args:
329
- technical_message: Technical optimization message
330
-
331
- Returns:
332
- User-friendly description of the optimization
333
- """
334
- # Convert technical messages to user-friendly descriptions
335
- if (
336
- "moved" in technical_message.lower()
337
- and "appendix" in technical_message.lower()
338
- ):
339
- file_name = technical_message.split("Moved ")[-1].split(
340
- " to appendix"
341
- )[0]
342
- return f"Moved large file '{file_name}' to organized appendix"
343
- elif "built structured appendix" in technical_message.lower():
344
- return "Organized file content into structured appendix for better AI processing"
345
- elif "moved directory" in technical_message.lower():
346
- return technical_message.replace(
347
- "Moved directory", "Organized directory"
348
- )
349
- else:
350
- return technical_message
351
-
352
304
 
353
305
  # Global progress reporter instance
354
306
  _progress_reporter: Optional[EnhancedProgressReporter] = None
@@ -367,16 +319,16 @@ def get_progress_reporter() -> EnhancedProgressReporter:
367
319
 
368
320
 
369
321
  def configure_progress_reporter(
370
- verbose: bool = False, progress_level: str = "basic"
322
+ verbose: bool = False, progress: str = "basic"
371
323
  ) -> None:
372
324
  """Configure the global progress reporter.
373
325
 
374
326
  Args:
375
327
  verbose: Enable verbose logging output
376
- progress_level: Progress level (none, basic, detailed)
328
+ progress: Progress level (none, basic, detailed)
377
329
  """
378
330
  global _progress_reporter
379
- _progress_reporter = EnhancedProgressReporter(verbose, progress_level)
331
+ _progress_reporter = EnhancedProgressReporter(verbose, progress)
380
332
 
381
333
 
382
334
  def report_phase(phase_name: str, emoji: str = "⚙️") -> None:
@@ -0,0 +1,128 @@
1
+ """Quick reference help system for ostruct CLI.
2
+
3
+ This module provides quick usage examples and patterns with rich formatting.
4
+ Uses rich-click formatting for beautiful, organized output.
5
+ """
6
+
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.syntax import Syntax
10
+ from rich.text import Text
11
+
12
+
13
+ def show_quick_ref_help() -> None:
14
+ """Display quick reference help with rich formatting."""
15
+ console = Console(stderr=True)
16
+
17
+ # Main title
18
+ title = Text("🚀 OSTRUCT QUICK REFERENCE", style="bold bright_blue")
19
+ console.print(title)
20
+ console.print()
21
+
22
+ # File Attachment System
23
+ attachment_content = """[bold bright_blue]--file[/bold bright_blue] alias file.txt 📄 Template access (default target)
24
+ [bold bright_blue]--file[/bold bright_blue] ci:data file.csv 💻 Code Interpreter upload
25
+ [bold bright_blue]--file[/bold bright_blue] fs:docs file.pdf 🔍 File Search vector store
26
+ [bold bright_blue]--file[/bold bright_blue] prompt:config config.yaml 📄 Template access (explicit)
27
+
28
+ [bold bright_blue]--dir[/bold bright_blue] alias ./src 📁 Directory attachment (template)
29
+ [bold bright_blue]--dir[/bold bright_blue] ci:data ./datasets 📂 Code execution directory
30
+ [bold bright_blue]--dir[/bold bright_blue] fs:docs ./documentation 📁 Search directory"""
31
+
32
+ attachment_panel = Panel(
33
+ attachment_content,
34
+ title="[bold]📎 File Attachment System[/bold]",
35
+ border_style="blue",
36
+ padding=(1, 2),
37
+ )
38
+ console.print(attachment_panel)
39
+
40
+ # Multi-tool Routing
41
+ routing_content = """[bold cyan]--file ci,fs:shared data.csv[/bold cyan] Share file between Code Interpreter and File Search
42
+ [bold cyan]--file prompt,ci:config settings.json[/bold cyan] Make file available in template AND Code Interpreter"""
43
+
44
+ routing_panel = Panel(
45
+ routing_content,
46
+ title="[bold]🔄 Multi-Tool Routing[/bold]",
47
+ border_style="cyan",
48
+ padding=(1, 2),
49
+ )
50
+ console.print(routing_panel)
51
+
52
+ # File Collections
53
+ collections_content = """[bold green]--collect all:files @filelist.txt[/bold green] 📄 Attach multiple files from list
54
+ [bold green]--collect ci:data @datasets.txt[/bold green] 💻 Upload file collection to Code Interpreter"""
55
+
56
+ collections_panel = Panel(
57
+ collections_content,
58
+ title="[bold]📝 File Collections[/bold]",
59
+ border_style="green",
60
+ padding=(1, 2),
61
+ )
62
+ console.print(collections_panel)
63
+
64
+ # Variables and Tools
65
+ vars_tools_content = """[bold yellow]Variables:[/bold yellow]
66
+ [cyan]-V name=value[/cyan] Simple string variables
67
+ [cyan]-J config='{"key":"value"}'[/cyan] JSON structured data
68
+
69
+ [bold yellow]Tools:[/bold yellow]
70
+ [cyan]--enable-tool web-search[/cyan] 🌐 Real-time web search for current info
71
+ [cyan]--mcp-server label@https://server.com/sse[/cyan] MCP server integration
72
+ [cyan]--timeout 7200[/cyan] 2-hour timeout for long operations"""
73
+
74
+ vars_tools_panel = Panel(
75
+ vars_tools_content,
76
+ title="[bold]🏷️ Variables & 🔌 Tools[/bold]",
77
+ border_style="yellow",
78
+ padding=(1, 2),
79
+ )
80
+ console.print(vars_tools_panel)
81
+
82
+ # Common Patterns
83
+ console.print(Text("⚡ Common Patterns", style="bold bright_green"))
84
+ console.print()
85
+
86
+ patterns = [
87
+ (
88
+ "Basic template rendering",
89
+ "ostruct run template.j2 schema.json -V env=prod",
90
+ ),
91
+ (
92
+ "Data analysis with Code Interpreter",
93
+ "ostruct run analysis.j2 schema.json --file ci:data data.csv -V task=analyze",
94
+ ),
95
+ (
96
+ "Document search + processing",
97
+ "ostruct run search.j2 schema.json --file fs:docs docs/ --file config config.yaml",
98
+ ),
99
+ (
100
+ "Multi-tool workflow",
101
+ "ostruct run workflow.j2 schema.json --file ci:data raw_data.csv --file fs:knowledge knowledge/ --file config config.json",
102
+ ),
103
+ (
104
+ "Current information research",
105
+ 'ostruct run research.j2 schema.json --enable-tool web-search -V topic="latest AI developments"',
106
+ ),
107
+ ]
108
+
109
+ for desc, cmd in patterns:
110
+ console.print(f"[dim]# {desc}[/dim]")
111
+ syntax = Syntax(
112
+ cmd, "bash", theme="monokai", background_color="default"
113
+ )
114
+ console.print(syntax)
115
+ console.print()
116
+
117
+ # Footer with help links
118
+ footer_content = """📖 [cyan]ostruct run --help[/cyan] Detailed options reference
119
+ 📖 [cyan]ostruct run --help-debug[/cyan] Troubleshooting guide
120
+ 📖 [dim]https://ostruct.readthedocs.io[/dim] Full documentation"""
121
+
122
+ footer_panel = Panel(
123
+ footer_content,
124
+ title="[bold]📖 More Help[/bold]",
125
+ border_style="blue",
126
+ padding=(1, 2),
127
+ )
128
+ console.print(footer_panel)