ostruct-cli 0.8.8__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.
- ostruct/cli/__init__.py +3 -15
- ostruct/cli/attachment_processor.py +455 -0
- ostruct/cli/attachment_template_bridge.py +973 -0
- ostruct/cli/cli.py +187 -33
- ostruct/cli/click_options.py +775 -692
- ostruct/cli/code_interpreter.py +195 -12
- ostruct/cli/commands/__init__.py +0 -3
- ostruct/cli/commands/run.py +289 -62
- ostruct/cli/config.py +23 -22
- ostruct/cli/constants.py +89 -0
- ostruct/cli/errors.py +191 -6
- ostruct/cli/explicit_file_processor.py +0 -15
- ostruct/cli/file_info.py +118 -14
- ostruct/cli/file_list.py +82 -1
- ostruct/cli/file_search.py +68 -2
- ostruct/cli/help_json.py +235 -0
- ostruct/cli/mcp_integration.py +13 -16
- ostruct/cli/params.py +217 -0
- ostruct/cli/plan_assembly.py +335 -0
- ostruct/cli/plan_printing.py +385 -0
- ostruct/cli/progress_reporting.py +8 -56
- ostruct/cli/quick_ref_help.py +128 -0
- ostruct/cli/rich_config.py +299 -0
- ostruct/cli/runner.py +397 -190
- ostruct/cli/security/__init__.py +2 -0
- ostruct/cli/security/allowed_checker.py +41 -0
- ostruct/cli/security/normalization.py +13 -9
- ostruct/cli/security/security_manager.py +558 -17
- ostruct/cli/security/types.py +15 -0
- ostruct/cli/template_debug.py +283 -261
- ostruct/cli/template_debug_help.py +233 -142
- ostruct/cli/template_env.py +46 -5
- ostruct/cli/template_filters.py +415 -8
- ostruct/cli/template_processor.py +240 -619
- ostruct/cli/template_rendering.py +49 -73
- ostruct/cli/template_validation.py +2 -1
- ostruct/cli/token_validation.py +35 -15
- ostruct/cli/types.py +15 -19
- ostruct/cli/unicode_compat.py +283 -0
- ostruct/cli/upload_manager.py +448 -0
- ostruct/cli/utils.py +30 -0
- ostruct/cli/validators.py +272 -54
- {ostruct_cli-0.8.8.dist-info → ostruct_cli-1.0.0.dist-info}/METADATA +292 -126
- ostruct_cli-1.0.0.dist-info/RECORD +80 -0
- ostruct/cli/commands/quick_ref.py +0 -54
- ostruct/cli/template_optimizer.py +0 -478
- ostruct_cli-0.8.8.dist-info/RECORD +0 -71
- {ostruct_cli-0.8.8.dist-info → ostruct_cli-1.0.0.dist-info}/LICENSE +0 -0
- {ostruct_cli-0.8.8.dist-info → ostruct_cli-1.0.0.dist-info}/WHEEL +0 -0
- {ostruct_cli-0.8.8.dist-info → ostruct_cli-1.0.0.dist-info}/entry_points.txt +0 -0
ostruct/cli/click_options.py
CHANGED
@@ -5,7 +5,8 @@ We isolate this code here and provide proper type annotations for Click's
|
|
5
5
|
decorator-based API.
|
6
6
|
"""
|
7
7
|
|
8
|
-
|
8
|
+
import logging
|
9
|
+
from typing import Any, Callable, List, TypeVar, Union, cast
|
9
10
|
|
10
11
|
import click
|
11
12
|
from click import Command
|
@@ -16,11 +17,10 @@ from ostruct.cli.errors import ( # noqa: F401 - Used in error handling
|
|
16
17
|
SystemPromptError,
|
17
18
|
TaskTemplateVariableError,
|
18
19
|
)
|
19
|
-
from ostruct.cli.validators import
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
)
|
20
|
+
from ostruct.cli.validators import validate_json_variable, validate_variable
|
21
|
+
|
22
|
+
from .constants import DefaultPaths
|
23
|
+
from .help_json import print_command_help_json as print_help_json
|
24
24
|
|
25
25
|
P = ParamSpec("P")
|
26
26
|
R = TypeVar("R")
|
@@ -28,6 +28,122 @@ F = TypeVar("F", bound=Callable[..., Any])
|
|
28
28
|
CommandDecorator = Callable[[F], Command]
|
29
29
|
DecoratedCommand = Union[Command, Callable[..., Any]]
|
30
30
|
|
31
|
+
logger = logging.getLogger(__name__)
|
32
|
+
|
33
|
+
|
34
|
+
def _handle_help_debug(
|
35
|
+
ctx: click.Context, param: click.Parameter, value: bool
|
36
|
+
) -> None:
|
37
|
+
"""Handle --help-debug flag by showing debug help and exiting."""
|
38
|
+
if not value or ctx.resilient_parsing:
|
39
|
+
return
|
40
|
+
|
41
|
+
from .template_debug_help import show_template_debug_help
|
42
|
+
|
43
|
+
show_template_debug_help()
|
44
|
+
ctx.exit()
|
45
|
+
|
46
|
+
|
47
|
+
def get_available_models() -> List[str]:
|
48
|
+
"""Get list of available models from registry that support structured output.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Sorted list of model names that support structured output
|
52
|
+
|
53
|
+
Note:
|
54
|
+
Registry handles its own caching internally.
|
55
|
+
Falls back to basic model list if registry fails.
|
56
|
+
"""
|
57
|
+
try:
|
58
|
+
from openai_model_registry import ModelRegistry
|
59
|
+
|
60
|
+
registry = ModelRegistry.get_instance()
|
61
|
+
all_models = list(registry.models)
|
62
|
+
|
63
|
+
# Filter to only models that support structured output
|
64
|
+
supported_models = []
|
65
|
+
for model in all_models:
|
66
|
+
try:
|
67
|
+
capabilities = registry.get_capabilities(model)
|
68
|
+
if getattr(capabilities, "supports_structured_output", True):
|
69
|
+
supported_models.append(model)
|
70
|
+
except Exception:
|
71
|
+
continue
|
72
|
+
|
73
|
+
return (
|
74
|
+
sorted(supported_models)
|
75
|
+
if supported_models
|
76
|
+
else _get_fallback_models()
|
77
|
+
)
|
78
|
+
|
79
|
+
except Exception as e:
|
80
|
+
logger.debug(f"Failed to load models from registry: {e}")
|
81
|
+
return _get_fallback_models()
|
82
|
+
|
83
|
+
|
84
|
+
def _get_fallback_models() -> List[str]:
|
85
|
+
"""Fallback model list when registry is unavailable."""
|
86
|
+
return ["gpt-4o", "gpt-4o-mini", "o1", "o1-mini", "o3-mini"]
|
87
|
+
|
88
|
+
|
89
|
+
class ModelChoice(click.Choice):
|
90
|
+
"""Custom Choice type with better error messages and help display for models."""
|
91
|
+
|
92
|
+
def convert(
|
93
|
+
self,
|
94
|
+
value: Any,
|
95
|
+
param: click.Parameter | None,
|
96
|
+
ctx: click.Context | None,
|
97
|
+
) -> str:
|
98
|
+
try:
|
99
|
+
return super().convert(value, param, ctx)
|
100
|
+
except click.BadParameter:
|
101
|
+
choices_list = list(self.choices)
|
102
|
+
available = ", ".join(choices_list[:5])
|
103
|
+
more_count = len(choices_list) - 5
|
104
|
+
more_text = f" (and {more_count} more)" if more_count > 0 else ""
|
105
|
+
|
106
|
+
raise click.BadParameter(
|
107
|
+
f"Invalid model '{value}'. Available models: {available}{more_text}.\n"
|
108
|
+
f"Run 'ostruct list-models' to see all {len(choices_list)} available models."
|
109
|
+
)
|
110
|
+
|
111
|
+
def shell_complete(
|
112
|
+
self, ctx: click.Context, param: click.Parameter, incomplete: str
|
113
|
+
) -> list:
|
114
|
+
"""Provide shell completion for model names."""
|
115
|
+
from click.shell_completion import CompletionItem
|
116
|
+
|
117
|
+
return [
|
118
|
+
CompletionItem(choice)
|
119
|
+
for choice in self.choices
|
120
|
+
if choice.startswith(incomplete)
|
121
|
+
]
|
122
|
+
|
123
|
+
def get_metavar(
|
124
|
+
self, param: click.Parameter, ctx: click.Context | None = None
|
125
|
+
) -> str:
|
126
|
+
"""Override metavar to show simple model info instead of complex list."""
|
127
|
+
choices_list = list(self.choices)
|
128
|
+
|
129
|
+
# Simple, clean display
|
130
|
+
return f"[{len(choices_list)} models available - run 'ostruct list-models' for full list]"
|
131
|
+
|
132
|
+
|
133
|
+
def create_model_choice() -> ModelChoice:
|
134
|
+
"""Create a ModelChoice object for model selection with error handling."""
|
135
|
+
try:
|
136
|
+
models = get_available_models()
|
137
|
+
if not models:
|
138
|
+
raise ValueError("No models available")
|
139
|
+
return ModelChoice(models, case_sensitive=True)
|
140
|
+
except Exception as e:
|
141
|
+
logger.warning(f"Failed to load dynamic model list: {e}")
|
142
|
+
logger.warning("Falling back to basic model validation")
|
143
|
+
|
144
|
+
fallback_models = _get_fallback_models()
|
145
|
+
return ModelChoice(fallback_models, case_sensitive=True)
|
146
|
+
|
31
147
|
|
32
148
|
def parse_feature_flags(
|
33
149
|
enabled_features: tuple[str, ...], disabled_features: tuple[str, ...]
|
@@ -79,347 +195,113 @@ def parse_feature_flags(
|
|
79
195
|
return parsed
|
80
196
|
|
81
197
|
|
82
|
-
|
83
|
-
"""Add debug-related CLI options."""
|
84
|
-
# Initial conversion to Command if needed
|
85
|
-
cmd: Any = f if isinstance(f, Command) else f
|
86
|
-
|
87
|
-
# Add options without redundant casts
|
88
|
-
cmd = click.option(
|
89
|
-
"--show-model-schema",
|
90
|
-
is_flag=True,
|
91
|
-
help="Show generated Pydantic model schema",
|
92
|
-
)(cmd)
|
198
|
+
# Helper functions moved to help_json.py for unified help system
|
93
199
|
|
94
|
-
cmd = click.option(
|
95
|
-
"--debug-validation",
|
96
|
-
is_flag=True,
|
97
|
-
help="Show detailed validation errors",
|
98
|
-
)(cmd)
|
99
|
-
|
100
|
-
cmd = click.option(
|
101
|
-
"--debug",
|
102
|
-
is_flag=True,
|
103
|
-
help="🐛 Enable debug-level logging including template expansion",
|
104
|
-
)(cmd)
|
105
|
-
|
106
|
-
cmd = click.option(
|
107
|
-
"--show-templates",
|
108
|
-
is_flag=True,
|
109
|
-
help="📝 Show expanded templates before sending to API",
|
110
|
-
)(cmd)
|
111
|
-
|
112
|
-
cmd = click.option(
|
113
|
-
"--debug-templates",
|
114
|
-
is_flag=True,
|
115
|
-
help="🔍 Enable detailed template expansion debugging with step-by-step analysis",
|
116
|
-
)(cmd)
|
117
|
-
|
118
|
-
cmd = click.option(
|
119
|
-
"--show-context",
|
120
|
-
is_flag=True,
|
121
|
-
help="📋 Show template variable context summary",
|
122
|
-
)(cmd)
|
123
|
-
|
124
|
-
cmd = click.option(
|
125
|
-
"--show-context-detailed",
|
126
|
-
is_flag=True,
|
127
|
-
help="📋 Show detailed template variable context with content preview",
|
128
|
-
)(cmd)
|
129
|
-
|
130
|
-
cmd = click.option(
|
131
|
-
"--show-pre-optimization",
|
132
|
-
is_flag=True,
|
133
|
-
help="🔧 Show template content before optimization is applied",
|
134
|
-
)(cmd)
|
135
|
-
|
136
|
-
cmd = click.option(
|
137
|
-
"--show-optimization-diff",
|
138
|
-
is_flag=True,
|
139
|
-
help="🔄 Show template optimization changes (before/after comparison)",
|
140
|
-
)(cmd)
|
141
|
-
|
142
|
-
cmd = click.option(
|
143
|
-
"--no-optimization",
|
144
|
-
is_flag=True,
|
145
|
-
help="⚡ Skip template optimization entirely for debugging",
|
146
|
-
)(cmd)
|
147
|
-
|
148
|
-
cmd = click.option(
|
149
|
-
"--show-optimization-steps",
|
150
|
-
is_flag=True,
|
151
|
-
help="🔧 Show detailed optimization step tracking with before/after changes",
|
152
|
-
)(cmd)
|
153
|
-
|
154
|
-
cmd = click.option(
|
155
|
-
"--optimization-step-detail",
|
156
|
-
type=click.Choice(["summary", "detailed"]),
|
157
|
-
default="summary",
|
158
|
-
help="📊 Level of optimization step detail (summary shows overview, detailed shows full diffs)",
|
159
|
-
)(cmd)
|
160
|
-
|
161
|
-
cmd = click.option(
|
162
|
-
"--help-debug",
|
163
|
-
is_flag=True,
|
164
|
-
help="📚 Show comprehensive template debugging help and examples",
|
165
|
-
)(cmd)
|
166
|
-
|
167
|
-
# Final cast to Command for return type
|
168
|
-
return cast(Command, cmd)
|
169
200
|
|
170
|
-
|
171
|
-
|
172
|
-
|
201
|
+
def debug_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
202
|
+
"""Add debug-related CLI options (now consolidated into debug_progress_options)."""
|
203
|
+
# All debug options have been moved to debug_progress_options for better grouping
|
204
|
+
# This function is kept for backward compatibility but does nothing
|
173
205
|
cmd: Any = f if isinstance(f, Command) else f
|
174
|
-
|
175
|
-
cmd = click.option(
|
176
|
-
"-f",
|
177
|
-
"--file",
|
178
|
-
"files",
|
179
|
-
multiple=True,
|
180
|
-
nargs=2,
|
181
|
-
metavar="<NAME> <PATH>",
|
182
|
-
callback=validate_name_path_pair,
|
183
|
-
help="""[LEGACY] Associate a file with a variable name for template access only.
|
184
|
-
The file will be available in your template as the specified variable.
|
185
|
-
For explicit tool routing, use -ft, -fc, or -fs instead.
|
186
|
-
Example: -f code main.py -f test test_main.py""",
|
187
|
-
shell_complete=click.Path(exists=True, file_okay=True, dir_okay=False),
|
188
|
-
)(cmd)
|
189
|
-
|
190
|
-
cmd = click.option(
|
191
|
-
"-d",
|
192
|
-
"--dir",
|
193
|
-
"dir",
|
194
|
-
multiple=True,
|
195
|
-
nargs=2,
|
196
|
-
metavar="<NAME> <DIR>",
|
197
|
-
callback=validate_name_path_pair,
|
198
|
-
help="""[LEGACY] Associate a directory with a variable name for template access only.
|
199
|
-
All files in the directory will be available in your template. Use -R for recursive scanning.
|
200
|
-
For explicit tool routing, use -dt, -dc, or -ds instead.
|
201
|
-
Example: -d src ./src""",
|
202
|
-
shell_complete=click.Path(exists=True, file_okay=False, dir_okay=True),
|
203
|
-
)(cmd)
|
204
|
-
|
205
|
-
# Template files with auto-naming ONLY (single argument)
|
206
|
-
cmd = click.option(
|
207
|
-
"-ft",
|
208
|
-
"--file-for-template",
|
209
|
-
"template_files",
|
210
|
-
multiple=True,
|
211
|
-
type=click.Path(exists=True, file_okay=True, dir_okay=False),
|
212
|
-
help="""📄 [TEMPLATE] Files for template access only (auto-naming). These files will be available
|
213
|
-
in your template but will not be uploaded to any tools. Use for configuration files,
|
214
|
-
small data files, or any content you want to reference in templates.
|
215
|
-
Format: -ft path (auto-generates variable name from filename).
|
216
|
-
Access file content: {{ variable.content }} (not just {{ variable }})
|
217
|
-
Example: -ft config.yaml → config_yaml variable, use {{ config_yaml.content }}""",
|
218
|
-
shell_complete=click.Path(exists=True, file_okay=True, dir_okay=False),
|
219
|
-
)(cmd)
|
220
|
-
|
221
|
-
# Template files with two-argument alias syntax (explicit naming)
|
222
|
-
cmd = click.option(
|
223
|
-
"--fta",
|
224
|
-
"--file-for-template-alias",
|
225
|
-
"template_file_aliases",
|
226
|
-
multiple=True,
|
227
|
-
nargs=2,
|
228
|
-
metavar="<NAME> <PATH>",
|
229
|
-
callback=validate_name_path_pair,
|
230
|
-
help="""📄 [TEMPLATE] Files for template with custom aliases. Use this for reusable
|
231
|
-
templates where you need stable variable names independent of file paths.
|
232
|
-
Format: --fta name path (supports tab completion for paths).
|
233
|
-
Access file content: {{ name.content }} (not just {{ name }})
|
234
|
-
Example: --fta config_data config.yaml → use {{ config_data.content }}""",
|
235
|
-
shell_complete=click.Path(exists=True, file_okay=True, dir_okay=False),
|
236
|
-
)(cmd)
|
237
|
-
|
238
|
-
cmd = click.option(
|
239
|
-
"-dt",
|
240
|
-
"--dir-for-template",
|
241
|
-
"template_dirs",
|
242
|
-
multiple=True,
|
243
|
-
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
244
|
-
help="""📁 [TEMPLATE] Directories for template access only (auto-naming). All files will be available
|
245
|
-
in your template but will not be uploaded to any tools. Use for project configurations,
|
246
|
-
reference data, or any directory content you want accessible in templates.
|
247
|
-
Format: -dt path (auto-generates variable name from directory name).
|
248
|
-
Example: -dt ./config -dt ./data""",
|
249
|
-
shell_complete=click.Path(exists=True, file_okay=False, dir_okay=True),
|
250
|
-
)(cmd)
|
251
|
-
|
252
|
-
# Template directories with two-argument alias syntax (explicit naming)
|
253
|
-
cmd = click.option(
|
254
|
-
"--dta",
|
255
|
-
"--dir-for-template-alias",
|
256
|
-
"template_dir_aliases",
|
257
|
-
multiple=True,
|
258
|
-
nargs=2,
|
259
|
-
metavar="<NAME> <PATH>",
|
260
|
-
callback=validate_name_path_pair,
|
261
|
-
help="""📁 [TEMPLATE] Directories for template with custom aliases. Use this for reusable
|
262
|
-
templates where you need stable variable names independent of directory paths.
|
263
|
-
Format: --dta name path (supports tab completion for paths).
|
264
|
-
Example: --dta config_data ./settings --dta source_code ./src""",
|
265
|
-
shell_complete=click.Path(exists=True, file_okay=False, dir_okay=True),
|
266
|
-
)(cmd)
|
267
|
-
|
268
|
-
cmd = click.option(
|
269
|
-
"--file-for",
|
270
|
-
"tool_files",
|
271
|
-
nargs=2,
|
272
|
-
multiple=True,
|
273
|
-
metavar="TOOL PATH",
|
274
|
-
help="""🔄 [ADVANCED] Route files to specific tools. Use this for precise control
|
275
|
-
over which tools receive which files. Supports tab completion for both tool names
|
276
|
-
and file paths.
|
277
|
-
Format: --file-for TOOL PATH
|
278
|
-
Examples:
|
279
|
-
--file-for code-interpreter analysis.py
|
280
|
-
--file-for file-search docs.pdf
|
281
|
-
--file-for template config.yaml""",
|
282
|
-
)(cmd)
|
283
|
-
|
284
|
-
cmd = click.option(
|
285
|
-
"-p",
|
286
|
-
"--pattern",
|
287
|
-
"patterns",
|
288
|
-
multiple=True,
|
289
|
-
nargs=2,
|
290
|
-
metavar="<NAME> <PATTERN>",
|
291
|
-
help="""[LEGACY] Associate a glob pattern with a variable name. Matching files will be
|
292
|
-
available in your template. Use -R for recursive matching.
|
293
|
-
Example: -p logs '*.log'""",
|
294
|
-
)(cmd)
|
295
|
-
|
296
|
-
cmd = click.option(
|
297
|
-
"-R",
|
298
|
-
"--recursive",
|
299
|
-
is_flag=True,
|
300
|
-
help="Process directories and patterns recursively",
|
301
|
-
)(cmd)
|
302
|
-
|
303
|
-
cmd = click.option(
|
304
|
-
"--base-dir",
|
305
|
-
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
306
|
-
help="""Base directory for resolving relative paths. All file operations will be
|
307
|
-
relative to this directory. Defaults to current directory.""",
|
308
|
-
shell_complete=click.Path(exists=True, file_okay=False, dir_okay=True),
|
309
|
-
)(cmd)
|
310
|
-
|
311
|
-
cmd = click.option(
|
312
|
-
"-A",
|
313
|
-
"--allow",
|
314
|
-
"allowed_dirs",
|
315
|
-
multiple=True,
|
316
|
-
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
317
|
-
help="""Add an allowed directory for security. Files must be within allowed
|
318
|
-
directories. Can be specified multiple times.""",
|
319
|
-
shell_complete=click.Path(exists=True, file_okay=False, dir_okay=True),
|
320
|
-
)(cmd)
|
321
|
-
|
322
|
-
cmd = click.option(
|
323
|
-
"--allowed-dir-file",
|
324
|
-
type=click.Path(exists=True, file_okay=True, dir_okay=False),
|
325
|
-
help="""File containing allowed directory paths, one per line. Lines starting
|
326
|
-
with # are treated as comments.""",
|
327
|
-
shell_complete=click.Path(exists=True, file_okay=True, dir_okay=False),
|
328
|
-
)(cmd)
|
329
|
-
|
330
206
|
return cast(Command, cmd)
|
331
207
|
|
332
208
|
|
333
|
-
def variable_options(
|
334
|
-
"""Add variable-related
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
209
|
+
def variable_options(cmd: Callable[..., Any]) -> Callable[..., Any]:
|
210
|
+
"""Add variable-related options to a command."""
|
211
|
+
# Apply options first (in reverse order since they stack)
|
212
|
+
for deco in (
|
213
|
+
click.option(
|
214
|
+
"-J",
|
215
|
+
"--json-var",
|
216
|
+
"json_var",
|
217
|
+
multiple=True,
|
218
|
+
metavar='name=\'{"json":"value"}\'',
|
219
|
+
callback=validate_json_variable,
|
220
|
+
help="""📋 [VARIABLES] Define a JSON variable for complex data structures.
|
221
|
+
JSON variables are parsed and available in templates as structured objects.
|
222
|
+
Format: name='{"key":"value"}'
|
223
|
+
Example: -J config='{"env":"prod","debug":true}'""",
|
224
|
+
),
|
225
|
+
click.option(
|
226
|
+
"-V",
|
227
|
+
"--var",
|
228
|
+
"var",
|
229
|
+
multiple=True,
|
230
|
+
metavar="name=value",
|
231
|
+
callback=validate_variable,
|
232
|
+
help="""🏷️ [VARIABLES] Define a simple string variable for template substitution.
|
345
233
|
Variables are available in your template as {{ variable_name }}.
|
346
234
|
Format: name=value
|
347
235
|
Example: -V debug=true -V env=prod""",
|
348
|
-
|
236
|
+
),
|
237
|
+
):
|
238
|
+
cmd = deco(cmd)
|
349
239
|
|
350
|
-
|
351
|
-
"-J",
|
352
|
-
"--json-var",
|
353
|
-
"json_var",
|
354
|
-
multiple=True,
|
355
|
-
metavar='name=\'{"json":"value"}\'',
|
356
|
-
callback=validate_json_variable,
|
357
|
-
help="""📋 [VARIABLES] Define a JSON variable for complex data structures.
|
358
|
-
JSON variables are parsed and available in templates as structured objects.
|
359
|
-
Format: name='{"key":"value"}'
|
360
|
-
Example: -J config='{"env":"prod","debug":true}'""",
|
361
|
-
)(cmd)
|
362
|
-
|
363
|
-
return cast(Command, cmd)
|
240
|
+
return cast(Callable[..., Any], cmd)
|
364
241
|
|
365
242
|
|
366
243
|
def model_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
367
244
|
"""Add model-related CLI options."""
|
368
245
|
cmd: Any = f if isinstance(f, Command) else f
|
369
246
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
247
|
+
# Create model choice with enhanced error handling
|
248
|
+
model_choice = create_model_choice()
|
249
|
+
|
250
|
+
# Ensure default is in the list
|
251
|
+
default_model = "gpt-4o"
|
252
|
+
choices_list = list(model_choice.choices)
|
253
|
+
if default_model not in choices_list and choices_list:
|
254
|
+
default_model = choices_list[0]
|
255
|
+
|
256
|
+
# Apply Model Configuration Options using click-option-group
|
257
|
+
# Apply options first (in reverse order since they stack)
|
258
|
+
for deco in (
|
259
|
+
click.option(
|
260
|
+
"--reasoning-effort",
|
261
|
+
type=click.Choice(["low", "medium", "high"]),
|
262
|
+
help="""Control reasoning effort (if supported by model).
|
263
|
+
Higher values may improve output quality but take longer.""",
|
264
|
+
),
|
265
|
+
click.option(
|
266
|
+
"--presence-penalty",
|
267
|
+
type=click.FloatRange(-2.0, 2.0),
|
268
|
+
help="""Presence penalty for text generation.
|
269
|
+
Range: -2.0 to 2.0. Positive values encourage new topics.""",
|
270
|
+
),
|
271
|
+
click.option(
|
272
|
+
"--frequency-penalty",
|
273
|
+
type=click.FloatRange(-2.0, 2.0),
|
274
|
+
help="""Frequency penalty for text generation.
|
275
|
+
Range: -2.0 to 2.0. Positive values reduce repetition.""",
|
276
|
+
),
|
277
|
+
click.option(
|
278
|
+
"--top-p",
|
279
|
+
type=click.FloatRange(0.0, 1.0),
|
280
|
+
help="""Top-p (nucleus) sampling parameter. Controls diversity.
|
281
|
+
Range: 0.0 to 1.0. Lower values are more focused.""",
|
282
|
+
),
|
283
|
+
click.option(
|
284
|
+
"--max-output-tokens",
|
285
|
+
type=click.IntRange(1, None),
|
286
|
+
help="""Maximum number of tokens in the output.
|
287
|
+
Higher values allow longer responses but cost more.""",
|
288
|
+
),
|
289
|
+
click.option(
|
290
|
+
"--temperature",
|
291
|
+
type=click.FloatRange(0.0, 2.0),
|
292
|
+
help="""Sampling temperature. Controls randomness in the output.
|
293
|
+
Range: 0.0 to 2.0. Lower values are more focused.""",
|
294
|
+
),
|
295
|
+
click.option(
|
296
|
+
"-m",
|
297
|
+
"--model",
|
298
|
+
type=model_choice,
|
299
|
+
default=default_model,
|
300
|
+
show_default=True,
|
301
|
+
help="OpenAI model to use. Must support structured output. Run 'ostruct list-models' for complete list.",
|
302
|
+
),
|
303
|
+
):
|
304
|
+
cmd = deco(cmd)
|
423
305
|
|
424
306
|
return cast(Command, cmd)
|
425
307
|
|
@@ -428,28 +310,33 @@ def system_prompt_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
428
310
|
"""Add system prompt related CLI options."""
|
429
311
|
cmd: Any = f if isinstance(f, Command) else f
|
430
312
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
313
|
+
# Apply System Prompt Options using click-option-group
|
314
|
+
# Apply options first (in reverse order since they stack)
|
315
|
+
for deco in (
|
316
|
+
click.option(
|
317
|
+
"--ignore-task-sysprompt",
|
318
|
+
is_flag=True,
|
319
|
+
help="""Ignore system prompt in task template. By default, system prompts
|
320
|
+
in template frontmatter are used.""",
|
321
|
+
),
|
322
|
+
click.option(
|
323
|
+
"--sys-file",
|
324
|
+
"system_prompt_file",
|
325
|
+
type=click.Path(exists=True, dir_okay=False),
|
326
|
+
help="""Load system prompt from file. The file should contain the prompt text.
|
443
327
|
Example: --sys-file prompts/code_review.txt""",
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
328
|
+
shell_complete=click.Path(
|
329
|
+
exists=True, file_okay=True, dir_okay=False
|
330
|
+
),
|
331
|
+
),
|
332
|
+
click.option(
|
333
|
+
"--sys-prompt",
|
334
|
+
"system_prompt",
|
335
|
+
help="""Provide system prompt directly. This sets the initial context
|
336
|
+
for the model. Example: --sys-prompt "You are a code reviewer.\"""",
|
337
|
+
),
|
338
|
+
):
|
339
|
+
cmd = deco(cmd)
|
453
340
|
|
454
341
|
return cast(Command, cmd)
|
455
342
|
|
@@ -458,20 +345,36 @@ def output_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
458
345
|
"""Add output-related CLI options."""
|
459
346
|
cmd: Any = f if isinstance(f, Command) else f
|
460
347
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
348
|
+
# Apply Output and Execution Options using click-option-group
|
349
|
+
# Apply options first (in reverse order since they stack)
|
350
|
+
for deco in (
|
351
|
+
click.option(
|
352
|
+
"--run-summary-json",
|
353
|
+
is_flag=True,
|
354
|
+
help="""Output run summary as JSON to stderr (cannot be used with --dry-run).
|
355
|
+
Provides machine-readable execution summary after live runs.""",
|
356
|
+
),
|
357
|
+
click.option(
|
358
|
+
"--dry-run-json",
|
359
|
+
is_flag=True,
|
360
|
+
help="""Output execution plan as JSON (requires --dry-run).
|
361
|
+
Outputs structured execution plan to stdout for programmatic consumption.""",
|
362
|
+
),
|
363
|
+
click.option(
|
364
|
+
"--dry-run",
|
365
|
+
is_flag=True,
|
366
|
+
help="""Validate and render but skip API call. Useful for testing
|
367
|
+
template rendering and validation.""",
|
368
|
+
),
|
369
|
+
click.option(
|
370
|
+
"--output-file",
|
371
|
+
type=click.Path(dir_okay=False),
|
372
|
+
help="""Write output to file instead of stdout.
|
373
|
+
Example: --output-file result.json""",
|
374
|
+
shell_complete=click.Path(file_okay=True, dir_okay=False),
|
375
|
+
),
|
376
|
+
):
|
377
|
+
cmd = deco(cmd)
|
475
378
|
|
476
379
|
return cast(Command, cmd)
|
477
380
|
|
@@ -480,26 +383,28 @@ def api_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
480
383
|
"""Add API-related CLI options."""
|
481
384
|
cmd: Any = f if isinstance(f, Command) else f
|
482
385
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
386
|
+
# Apply Configuration and API Options using click-option-group
|
387
|
+
# Apply options first (in reverse order since they stack)
|
388
|
+
for deco in (
|
389
|
+
click.option(
|
390
|
+
"--timeout",
|
391
|
+
type=click.FloatRange(1.0, None),
|
392
|
+
default=60.0,
|
393
|
+
show_default=True,
|
394
|
+
help="Timeout in seconds for OpenAI API calls.",
|
395
|
+
),
|
396
|
+
click.option(
|
397
|
+
"--api-key",
|
398
|
+
help="""OpenAI API key. If not provided, uses OPENAI_API_KEY
|
399
|
+
environment variable.""",
|
400
|
+
),
|
401
|
+
click.option(
|
402
|
+
"--config",
|
403
|
+
type=click.Path(exists=True),
|
404
|
+
help="Configuration file path (default: ostruct.yaml)",
|
405
|
+
),
|
406
|
+
):
|
407
|
+
cmd = deco(cmd)
|
503
408
|
|
504
409
|
return cast(Command, cmd)
|
505
410
|
|
@@ -508,246 +413,159 @@ def mcp_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
508
413
|
"""Add MCP (Model Context Protocol) server CLI options."""
|
509
414
|
cmd: Any = f if isinstance(f, Command) else f
|
510
415
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
416
|
+
# Apply MCP Server Configuration Options using click-option-group
|
417
|
+
# Apply options first (in reverse order since they stack)
|
418
|
+
for deco in (
|
419
|
+
click.option(
|
420
|
+
"--mcp-headers",
|
421
|
+
help="""JSON string of headers for MCP servers.
|
422
|
+
Example: --mcp-headers '{"Authorization": "Bearer token"}'""",
|
423
|
+
),
|
424
|
+
click.option(
|
425
|
+
"--mcp-require-approval",
|
426
|
+
type=click.Choice(["always", "never"]),
|
427
|
+
default="never",
|
428
|
+
show_default=True,
|
429
|
+
help="""Approval level for MCP tool usage. CLI usage requires 'never'.""",
|
430
|
+
),
|
431
|
+
click.option(
|
432
|
+
"--mcp-allowed-tools",
|
433
|
+
"mcp_allowed_tools",
|
434
|
+
multiple=True,
|
435
|
+
help="""Allowed tools per server. Format: server_label:tool1,tool2
|
436
|
+
Example: --mcp-allowed-tools deepwiki:search,summary""",
|
437
|
+
),
|
438
|
+
click.option(
|
439
|
+
"--mcp-server",
|
440
|
+
"mcp_servers",
|
441
|
+
multiple=True,
|
442
|
+
help="""🔌 [MCP] Connect to Model Context Protocol server for extended capabilities.
|
516
443
|
MCP servers provide additional tools like web search, databases, APIs, etc.
|
517
444
|
Format: [label@]url
|
518
445
|
Example: --mcp-server deepwiki@https://mcp.deepwiki.com/sse""",
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
"--mcp-allowed-tools",
|
523
|
-
"mcp_allowed_tools",
|
524
|
-
multiple=True,
|
525
|
-
help="""Allowed tools per server. Format: server_label:tool1,tool2
|
526
|
-
Example: --mcp-allowed-tools deepwiki:search,summary""",
|
527
|
-
)(cmd)
|
528
|
-
|
529
|
-
cmd = click.option(
|
530
|
-
"--mcp-require-approval",
|
531
|
-
type=click.Choice(["always", "never"]),
|
532
|
-
default="never",
|
533
|
-
show_default=True,
|
534
|
-
help="""Approval level for MCP tool usage. CLI usage requires 'never'.""",
|
535
|
-
)(cmd)
|
536
|
-
|
537
|
-
cmd = click.option(
|
538
|
-
"--mcp-headers",
|
539
|
-
help="""JSON string of headers for MCP servers.
|
540
|
-
Example: --mcp-headers '{"Authorization": "Bearer token"}'""",
|
541
|
-
)(cmd)
|
446
|
+
),
|
447
|
+
):
|
448
|
+
cmd = deco(cmd)
|
542
449
|
|
543
450
|
return cast(Command, cmd)
|
544
451
|
|
545
452
|
|
546
|
-
def
|
547
|
-
"""Add
|
453
|
+
def feature_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
454
|
+
"""Add feature flag and configuration options (without legacy file routing)."""
|
548
455
|
cmd: Any = f if isinstance(f, Command) else f
|
549
456
|
|
550
|
-
# Code
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
"
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
shell_complete=click.Path(exists=True, file_okay=False, dir_okay=True),
|
607
|
-
)(cmd)
|
608
|
-
|
609
|
-
cmd = click.option(
|
610
|
-
"--code-interpreter-download-dir",
|
611
|
-
type=click.Path(file_okay=False, dir_okay=True),
|
612
|
-
default="./downloads",
|
613
|
-
show_default=True,
|
614
|
-
help="""Directory to save files generated by Code Interpreter.
|
615
|
-
Example: --code-interpreter-download-dir ./results""",
|
616
|
-
shell_complete=click.Path(file_okay=False, dir_okay=True),
|
617
|
-
)(cmd)
|
618
|
-
|
619
|
-
cmd = click.option(
|
620
|
-
"--code-interpreter-cleanup",
|
621
|
-
is_flag=True,
|
622
|
-
default=True,
|
623
|
-
show_default=True,
|
624
|
-
help="""Clean up uploaded files after execution to save storage quota.""",
|
625
|
-
)(cmd)
|
626
|
-
|
627
|
-
# Feature flags for experimental features
|
628
|
-
cmd = click.option(
|
629
|
-
"--enable-feature",
|
630
|
-
"enabled_features",
|
631
|
-
multiple=True,
|
632
|
-
metavar="<FEATURE>",
|
633
|
-
help="""🔧 [EXPERIMENTAL] Enable experimental features.
|
457
|
+
# Apply Code Interpreter Configuration Options using click-option-group
|
458
|
+
# Apply options first (in reverse order since they stack)
|
459
|
+
for deco in (
|
460
|
+
click.option(
|
461
|
+
"--ci-cleanup",
|
462
|
+
is_flag=True,
|
463
|
+
default=True,
|
464
|
+
show_default=True,
|
465
|
+
help="""🤖 [CODE INTERPRETER] Clean up uploaded files after execution to save storage quota.""",
|
466
|
+
),
|
467
|
+
click.option(
|
468
|
+
"--ci-duplicate-outputs",
|
469
|
+
type=click.Choice(["overwrite", "rename", "skip"]),
|
470
|
+
default=None, # Will use config default or fallback to "overwrite"
|
471
|
+
show_default="overwrite",
|
472
|
+
help="""🤖 [CODE INTERPRETER] Handle duplicate output file names.
|
473
|
+
'overwrite' replaces existing files (default),
|
474
|
+
'rename' creates unique names (file_1.txt, file_2.txt),
|
475
|
+
'skip' ignores files that already exist.
|
476
|
+
Example: --ci-duplicate-outputs rename""",
|
477
|
+
),
|
478
|
+
click.option(
|
479
|
+
"--ci-download-dir",
|
480
|
+
type=click.Path(file_okay=False, dir_okay=True),
|
481
|
+
default=None, # Will use config default or fallback in runner.py
|
482
|
+
show_default=DefaultPaths.CODE_INTERPRETER_OUTPUT_DIR,
|
483
|
+
help="""🤖 [CODE INTERPRETER] Directory to save files generated by Code Interpreter.
|
484
|
+
Example: --ci-download-dir ./results""",
|
485
|
+
shell_complete=click.Path(file_okay=False, dir_okay=True),
|
486
|
+
),
|
487
|
+
):
|
488
|
+
cmd = deco(cmd)
|
489
|
+
|
490
|
+
# Apply the group decorator LAST so it sees all the options
|
491
|
+
cmd = cmd
|
492
|
+
|
493
|
+
# Apply Experimental Features Options using click-option-group
|
494
|
+
# Apply options first (in reverse order since they stack)
|
495
|
+
for deco in (
|
496
|
+
click.option(
|
497
|
+
"--disable-feature",
|
498
|
+
"disabled_features",
|
499
|
+
multiple=True,
|
500
|
+
metavar="<FEATURE>",
|
501
|
+
help="""🔧 [EXPERIMENTAL] Disable experimental features.
|
502
|
+
Available features:
|
503
|
+
• ci-download-hack - Force single-pass mode for Code Interpreter downloads.
|
504
|
+
Overrides config file setting.
|
505
|
+
Example: --disable-feature ci-download-hack""",
|
506
|
+
),
|
507
|
+
click.option(
|
508
|
+
"--enable-feature",
|
509
|
+
"enabled_features",
|
510
|
+
multiple=True,
|
511
|
+
metavar="<FEATURE>",
|
512
|
+
help="""🔧 [EXPERIMENTAL] Enable experimental features.
|
634
513
|
Available features:
|
635
514
|
• ci-download-hack - Enable two-pass sentinel mode for reliable Code Interpreter
|
636
515
|
file downloads with structured output. Overrides config file setting.
|
637
516
|
Example: --enable-feature ci-download-hack""",
|
638
|
-
|
517
|
+
),
|
518
|
+
):
|
519
|
+
cmd = deco(cmd)
|
639
520
|
|
640
|
-
|
641
|
-
|
642
|
-
"disabled_features",
|
643
|
-
multiple=True,
|
644
|
-
metavar="<FEATURE>",
|
645
|
-
help="""🔧 [EXPERIMENTAL] Disable experimental features.
|
646
|
-
Available features:
|
647
|
-
• ci-download-hack - Force single-pass mode for Code Interpreter downloads.
|
648
|
-
Overrides config file setting.
|
649
|
-
Example: --disable-feature ci-download-hack""",
|
650
|
-
)(cmd)
|
521
|
+
# Apply the group decorator LAST so it sees all the options
|
522
|
+
cmd = cmd
|
651
523
|
|
652
524
|
return cast(Command, cmd)
|
653
525
|
|
654
526
|
|
655
|
-
def
|
656
|
-
|
527
|
+
def file_search_config_options(
|
528
|
+
f: Union[Command, Callable[..., Any]],
|
529
|
+
) -> Command:
|
530
|
+
"""Add File Search configuration options (without legacy file routing)."""
|
657
531
|
cmd: Any = f if isinstance(f, Command) else f
|
658
532
|
|
659
|
-
# File
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
help="""📁 [FILE SEARCH] Directories to upload for semantic search (auto-naming). All files in the
|
696
|
-
directory will be processed into a searchable vector store. Use for documentation
|
697
|
-
directories, knowledge bases, or any collection of searchable documents.
|
698
|
-
Format: -ds path (auto-generates variable name from directory name).
|
699
|
-
Example: -ds ./docs -ds ./manuals""",
|
700
|
-
shell_complete=click.Path(exists=True, file_okay=False, dir_okay=True),
|
701
|
-
)(cmd)
|
702
|
-
|
703
|
-
# File search directories with two-argument alias syntax (explicit naming)
|
704
|
-
cmd = click.option(
|
705
|
-
"--dsa",
|
706
|
-
"--dir-for-search-alias",
|
707
|
-
"file_search_dir_aliases",
|
708
|
-
multiple=True,
|
709
|
-
nargs=2,
|
710
|
-
metavar="<NAME> <PATH>",
|
711
|
-
callback=validate_name_path_pair,
|
712
|
-
help="""📁 [FILE SEARCH] Directories for search with custom aliases.
|
713
|
-
Format: --dsa name path (supports tab completion for paths).
|
714
|
-
Example: --dsa documentation ./docs --dsa knowledge_base ./manuals""",
|
715
|
-
shell_complete=click.Path(exists=True, file_okay=False, dir_okay=True),
|
716
|
-
)(cmd)
|
717
|
-
|
718
|
-
cmd = click.option(
|
719
|
-
"--file-search-vector-store-name",
|
720
|
-
default="ostruct_search",
|
721
|
-
show_default=True,
|
722
|
-
help="""Name for the vector store created for File Search.
|
723
|
-
Example: --file-search-vector-store-name project_docs""",
|
724
|
-
)(cmd)
|
725
|
-
|
726
|
-
cmd = click.option(
|
727
|
-
"--file-search-cleanup",
|
728
|
-
is_flag=True,
|
729
|
-
default=True,
|
730
|
-
show_default=True,
|
731
|
-
help="""Clean up uploaded files and vector stores after execution.""",
|
732
|
-
)(cmd)
|
733
|
-
|
734
|
-
cmd = click.option(
|
735
|
-
"--file-search-retry-count",
|
736
|
-
type=click.IntRange(1, 10),
|
737
|
-
default=3,
|
738
|
-
show_default=True,
|
739
|
-
help="""Number of retry attempts for File Search operations.
|
740
|
-
Higher values improve reliability for intermittent failures.""",
|
741
|
-
)(cmd)
|
742
|
-
|
743
|
-
cmd = click.option(
|
744
|
-
"--file-search-timeout",
|
745
|
-
type=click.FloatRange(10.0, 300.0),
|
746
|
-
default=60.0,
|
747
|
-
show_default=True,
|
748
|
-
help="""Timeout in seconds for vector store indexing.
|
749
|
-
Typically instant but may take longer for large files.""",
|
750
|
-
)(cmd)
|
533
|
+
# Apply File Search Configuration Options using click-option-group
|
534
|
+
# Apply options first (in reverse order since they stack)
|
535
|
+
for deco in (
|
536
|
+
click.option(
|
537
|
+
"--fs-timeout",
|
538
|
+
type=float,
|
539
|
+
default=60.0,
|
540
|
+
help="""📁 [FILE SEARCH] Timeout in seconds for vector store indexing operations.
|
541
|
+
Increase for large file uploads.""",
|
542
|
+
),
|
543
|
+
click.option(
|
544
|
+
"--fs-retries",
|
545
|
+
type=int,
|
546
|
+
default=3,
|
547
|
+
help="""📁 [FILE SEARCH] Number of retry attempts for file search operations.
|
548
|
+
Increase for unreliable network connections.""",
|
549
|
+
),
|
550
|
+
click.option(
|
551
|
+
"--fs-cleanup",
|
552
|
+
is_flag=True,
|
553
|
+
default=True,
|
554
|
+
help="""📁 [FILE SEARCH] Clean up uploaded files and vector stores after use.
|
555
|
+
Disable with --no-fs-cleanup to keep files for debugging.""",
|
556
|
+
),
|
557
|
+
click.option(
|
558
|
+
"--fs-store-name",
|
559
|
+
type=str,
|
560
|
+
default="ostruct_search",
|
561
|
+
help="""📁 [FILE SEARCH] Name for the vector store used for file search.
|
562
|
+
Example: --fs-store-name project_docs""",
|
563
|
+
),
|
564
|
+
):
|
565
|
+
cmd = deco(cmd)
|
566
|
+
|
567
|
+
# Apply the group decorator LAST so it sees all the options
|
568
|
+
cmd = cmd
|
751
569
|
|
752
570
|
return cast(Command, cmd)
|
753
571
|
|
@@ -756,51 +574,38 @@ def web_search_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
756
574
|
"""Add Web Search CLI options."""
|
757
575
|
cmd: Any = f if isinstance(f, Command) else f
|
758
576
|
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
"--user-country",
|
779
|
-
type=str,
|
780
|
-
help="""🌐 [WEB SEARCH] Specify user country for geographically tailored search results.
|
781
|
-
Used to improve search relevance by location (e.g., 'US', 'UK', 'Germany').""",
|
782
|
-
)(cmd)
|
783
|
-
|
784
|
-
cmd = click.option(
|
785
|
-
"--user-city",
|
786
|
-
type=str,
|
787
|
-
help="""🌐 [WEB SEARCH] Specify user city for geographically tailored search results.
|
577
|
+
# Apply Web Search Configuration Options using click-option-group
|
578
|
+
# Apply options first (in reverse order since they stack)
|
579
|
+
for deco in (
|
580
|
+
click.option(
|
581
|
+
"--ws-context-size",
|
582
|
+
type=click.Choice(["low", "medium", "high"]),
|
583
|
+
help="""🌐 [WEB SEARCH] Control the amount of content retrieved from search results.
|
584
|
+
'low' = brief snippets, 'medium' = balanced content, 'high' = comprehensive content.""",
|
585
|
+
),
|
586
|
+
click.option(
|
587
|
+
"--ws-region",
|
588
|
+
type=str,
|
589
|
+
help="""🌐 [WEB SEARCH] Specify user region/state for geographically tailored search results.
|
590
|
+
Used to improve search relevance by location (e.g., 'California', 'Texas').""",
|
591
|
+
),
|
592
|
+
click.option(
|
593
|
+
"--ws-city",
|
594
|
+
type=str,
|
595
|
+
help="""🌐 [WEB SEARCH] Specify user city for geographically tailored search results.
|
788
596
|
Used to improve search relevance by location (e.g., 'San Francisco', 'London').""",
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
)
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
help="""🌐 [WEB SEARCH] Control the amount of content retrieved from web pages.
|
802
|
-
'low' retrieves minimal content, 'high' retrieves comprehensive content. Default: medium.""",
|
803
|
-
)(cmd)
|
597
|
+
),
|
598
|
+
click.option(
|
599
|
+
"--ws-country",
|
600
|
+
type=str,
|
601
|
+
help="""🌐 [WEB SEARCH] Specify user country for geographically tailored search results.
|
602
|
+
Used to improve search relevance by location (e.g., 'US', 'UK', 'Germany').""",
|
603
|
+
),
|
604
|
+
):
|
605
|
+
cmd = deco(cmd)
|
606
|
+
|
607
|
+
# Apply the group decorator LAST so it sees all the options
|
608
|
+
cmd = cmd
|
804
609
|
|
805
610
|
return cast(Command, cmd)
|
806
611
|
|
@@ -809,73 +614,351 @@ def tool_toggle_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
809
614
|
"""Add universal tool toggle CLI options."""
|
810
615
|
cmd: Any = f if isinstance(f, Command) else f
|
811
616
|
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
617
|
+
# Apply Tool Integration Options using click-option-group
|
618
|
+
# Apply options first (in reverse order since they stack)
|
619
|
+
for deco in (
|
620
|
+
click.option(
|
621
|
+
"--disable-tool",
|
622
|
+
"disabled_tools",
|
623
|
+
multiple=True,
|
624
|
+
metavar="<TOOL>",
|
625
|
+
help="""🔧 [TOOL TOGGLES] Disable a tool for this run (repeatable).
|
626
|
+
Overrides configuration file and implicit activation.
|
627
|
+
Available tools: code-interpreter, file-search, web-search, mcp
|
628
|
+
Example: --disable-tool web-search --disable-tool mcp""",
|
629
|
+
),
|
630
|
+
click.option(
|
631
|
+
"--enable-tool",
|
632
|
+
"enabled_tools",
|
633
|
+
multiple=True,
|
634
|
+
metavar="<TOOL>",
|
635
|
+
help="""🔧 [TOOL TOGGLES] Enable a tool for this run (repeatable).
|
818
636
|
Overrides configuration file and implicit activation.
|
819
637
|
Available tools: code-interpreter, file-search, web-search, mcp
|
820
638
|
Example: --enable-tool code-interpreter --enable-tool web-search""",
|
821
|
-
|
639
|
+
),
|
640
|
+
):
|
641
|
+
cmd = deco(cmd)
|
822
642
|
|
823
|
-
|
824
|
-
|
825
|
-
"disabled_tools",
|
826
|
-
multiple=True,
|
827
|
-
metavar="<TOOL>",
|
828
|
-
help="""🔧 [TOOL TOGGLES] Disable a tool for this run (repeatable).
|
829
|
-
Overrides configuration file and implicit activation.
|
830
|
-
Available tools: code-interpreter, file-search, web-search, mcp
|
831
|
-
Example: --disable-tool web-search --disable-tool mcp""",
|
832
|
-
)(cmd)
|
643
|
+
# Apply the group decorator LAST so it sees all the options
|
644
|
+
cmd = cmd
|
833
645
|
|
834
646
|
return cast(Command, cmd)
|
835
647
|
|
836
648
|
|
837
649
|
def debug_progress_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
838
650
|
"""Add debugging and progress CLI options."""
|
651
|
+
# Import the new infrastructure for template debug
|
652
|
+
from .template_debug import parse_td
|
653
|
+
|
839
654
|
cmd: Any = f if isinstance(f, Command) else f
|
840
655
|
|
841
|
-
|
842
|
-
|
843
|
-
|
656
|
+
# Apply Debug and Development Options using click-option-group
|
657
|
+
# Apply options first (in reverse order since they stack)
|
658
|
+
for deco in (
|
659
|
+
click.option(
|
660
|
+
"--help-debug",
|
661
|
+
is_flag=True,
|
662
|
+
is_eager=True,
|
663
|
+
expose_value=False,
|
664
|
+
callback=lambda ctx, param, value: _handle_help_debug(
|
665
|
+
ctx, param, value
|
666
|
+
),
|
667
|
+
help="📚 Show comprehensive template debugging help and examples",
|
668
|
+
),
|
669
|
+
click.option(
|
670
|
+
"--debug",
|
671
|
+
is_flag=True,
|
672
|
+
help="🐛 Enable debug-level logging including template expansion",
|
673
|
+
),
|
674
|
+
click.option(
|
675
|
+
"--debug-validation",
|
676
|
+
is_flag=True,
|
677
|
+
help="Show detailed validation errors",
|
678
|
+
),
|
679
|
+
click.option(
|
680
|
+
"--show-model-schema",
|
681
|
+
is_flag=True,
|
682
|
+
help="Show generated Pydantic model schema",
|
683
|
+
),
|
684
|
+
click.option(
|
685
|
+
"-t",
|
686
|
+
"--template-debug",
|
687
|
+
metavar="CAPACITIES",
|
688
|
+
default=None,
|
689
|
+
is_flag=False,
|
690
|
+
flag_value="all",
|
691
|
+
expose_value=False,
|
692
|
+
callback=lambda ctx, p, v: (
|
693
|
+
ctx.obj.setdefault("_template_debug_caps", parse_td(v))
|
694
|
+
if ctx.obj is not None and v is not None
|
695
|
+
else None
|
696
|
+
),
|
697
|
+
help="🔍 Debug prompt-template expansion. "
|
698
|
+
"Capacities: pre-expand,vars,preview,steps,post-expand "
|
699
|
+
"(comma list or 'all'). Use -t CAPACITIES or bare -t for all capacities.",
|
700
|
+
),
|
701
|
+
click.option("--verbose", is_flag=True, help="Enable verbose logging"),
|
702
|
+
click.option(
|
703
|
+
"--progress",
|
704
|
+
type=click.Choice(["none", "basic", "detailed"]),
|
705
|
+
default="basic",
|
706
|
+
show_default=True,
|
707
|
+
help="""Control progress display. 'none' disables progress indicators,
|
708
|
+
'basic' shows key steps, 'detailed' shows all operations.""",
|
709
|
+
),
|
710
|
+
):
|
711
|
+
cmd = deco(cmd)
|
712
|
+
|
713
|
+
# Apply the group decorator LAST so it sees all the options
|
714
|
+
cmd = cmd
|
715
|
+
|
716
|
+
return cast(Command, cmd)
|
717
|
+
|
718
|
+
|
719
|
+
def security_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
720
|
+
"""Add path security and allowlist CLI options."""
|
721
|
+
cmd: Any = f if isinstance(f, Command) else f
|
722
|
+
|
723
|
+
# Apply Security and Path Control Options using click-option-group
|
724
|
+
# Apply options first (in reverse order since they stack)
|
725
|
+
for deco in (
|
726
|
+
click.option(
|
727
|
+
"--allow-list",
|
728
|
+
"allow_list",
|
729
|
+
multiple=True,
|
730
|
+
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
|
731
|
+
help="📋 Allow paths from file list for strict/warn mode (repeatable)",
|
732
|
+
),
|
733
|
+
click.option(
|
734
|
+
"--allow-file",
|
735
|
+
"allow_file",
|
736
|
+
multiple=True,
|
737
|
+
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
|
738
|
+
help="📄 Allow specific file for strict/warn mode (repeatable)",
|
739
|
+
),
|
740
|
+
click.option(
|
741
|
+
"--allow",
|
742
|
+
"allow_dir",
|
743
|
+
multiple=True,
|
744
|
+
type=click.Path(exists=True, file_okay=False),
|
745
|
+
help="🗂️ Allow directory for strict/warn mode (repeatable)",
|
746
|
+
),
|
747
|
+
click.option(
|
748
|
+
"-S",
|
749
|
+
"--path-security",
|
750
|
+
type=click.Choice(
|
751
|
+
["permissive", "warn", "strict"], case_sensitive=False
|
752
|
+
),
|
753
|
+
help="🔒 Path security mode: permissive (allow all), warn (log warnings), strict (allowlist only)",
|
754
|
+
),
|
755
|
+
):
|
756
|
+
cmd = deco(cmd)
|
757
|
+
|
758
|
+
# Apply the group decorator LAST so it sees all the options
|
759
|
+
cmd = cmd
|
760
|
+
|
761
|
+
return cast(Command, cmd)
|
844
762
|
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
show_default=True,
|
850
|
-
help="""Control progress verbosity. 'none' shows no progress,
|
851
|
-
'basic' shows key steps, 'detailed' shows all steps.""",
|
852
|
-
)(cmd)
|
763
|
+
|
764
|
+
def help_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
765
|
+
"""Add help-related CLI options."""
|
766
|
+
cmd: Any = f if isinstance(f, Command) else f
|
853
767
|
|
854
768
|
cmd = click.option(
|
855
|
-
"--
|
769
|
+
"--help-json",
|
770
|
+
is_flag=True,
|
771
|
+
callback=print_help_json,
|
772
|
+
expose_value=False,
|
773
|
+
is_eager=True,
|
774
|
+
hidden=True, # Hide from help output - feature not ready for release
|
775
|
+
help="📖 Output command help in JSON format for programmatic consumption",
|
856
776
|
)(cmd)
|
857
777
|
|
858
778
|
return cast(Command, cmd)
|
859
779
|
|
860
780
|
|
781
|
+
def file_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
782
|
+
"""Add file attachment options with target/alias syntax."""
|
783
|
+
|
784
|
+
# Import validation functions here to avoid circular imports
|
785
|
+
def validate_attachment_file(
|
786
|
+
ctx: click.Context, param: click.Parameter, value: Any
|
787
|
+
) -> Any:
|
788
|
+
from .params import normalise_targets, validate_attachment_alias
|
789
|
+
|
790
|
+
if not value:
|
791
|
+
return []
|
792
|
+
|
793
|
+
result = []
|
794
|
+
for spec, path in value:
|
795
|
+
# Parse spec part: [targets:]alias
|
796
|
+
if ":" in spec:
|
797
|
+
# Check for Windows drive letter false positive
|
798
|
+
if len(spec) == 2 and spec[1] == ":" and spec[0].isalpha():
|
799
|
+
prefix, alias = "prompt", spec
|
800
|
+
else:
|
801
|
+
prefix, alias = spec.split(":", 1)
|
802
|
+
else:
|
803
|
+
prefix, alias = "prompt", spec
|
804
|
+
|
805
|
+
# Normalize targets
|
806
|
+
try:
|
807
|
+
targets = normalise_targets(prefix)
|
808
|
+
except click.BadParameter as e:
|
809
|
+
raise click.BadParameter(
|
810
|
+
f"Invalid target(s) in '{prefix}' for {param.name}. {e}"
|
811
|
+
)
|
812
|
+
|
813
|
+
# Validate alias
|
814
|
+
try:
|
815
|
+
alias = validate_attachment_alias(alias)
|
816
|
+
except click.BadParameter as e:
|
817
|
+
raise click.BadParameter(
|
818
|
+
f"Invalid alias for {param.name}: {e}"
|
819
|
+
)
|
820
|
+
|
821
|
+
result.append(
|
822
|
+
{
|
823
|
+
"alias": alias,
|
824
|
+
"path": path,
|
825
|
+
"targets": targets,
|
826
|
+
"recursive": False,
|
827
|
+
"pattern": None,
|
828
|
+
}
|
829
|
+
)
|
830
|
+
|
831
|
+
return result
|
832
|
+
|
833
|
+
def validate_attachment_dir(
|
834
|
+
ctx: click.Context, param: click.Parameter, value: Any
|
835
|
+
) -> Any:
|
836
|
+
return validate_attachment_file(ctx, param, value)
|
837
|
+
|
838
|
+
def validate_attachment_collect(
|
839
|
+
ctx: click.Context, param: click.Parameter, value: Any
|
840
|
+
) -> Any:
|
841
|
+
if not value:
|
842
|
+
return []
|
843
|
+
|
844
|
+
result = []
|
845
|
+
for spec, path in value:
|
846
|
+
# Parse spec part: [targets:]alias
|
847
|
+
if ":" in spec:
|
848
|
+
if len(spec) == 2 and spec[1] == ":" and spec[0].isalpha():
|
849
|
+
prefix, alias = "prompt", spec
|
850
|
+
else:
|
851
|
+
prefix, alias = spec.split(":", 1)
|
852
|
+
else:
|
853
|
+
prefix, alias = "prompt", spec
|
854
|
+
|
855
|
+
# Handle collect @filelist syntax
|
856
|
+
processed_path = path
|
857
|
+
if path.startswith("@"):
|
858
|
+
filelist_path = path[1:] # Remove @
|
859
|
+
if not filelist_path:
|
860
|
+
raise click.BadParameter(
|
861
|
+
f"Filelist path cannot be empty after @ for {param.name}"
|
862
|
+
)
|
863
|
+
processed_path = ("@", filelist_path)
|
864
|
+
|
865
|
+
result.append(
|
866
|
+
{
|
867
|
+
"alias": alias,
|
868
|
+
"path": processed_path,
|
869
|
+
"targets": set([prefix.lower()]),
|
870
|
+
"recursive": False,
|
871
|
+
"pattern": None,
|
872
|
+
}
|
873
|
+
)
|
874
|
+
|
875
|
+
return result
|
876
|
+
|
877
|
+
# Apply File Attachment Options using click-option-group
|
878
|
+
# Fix: Attach options first, then wrap them in the group decorator last
|
879
|
+
cmd: Any = f if isinstance(f, Command) else f
|
880
|
+
|
881
|
+
# Attach options first (in reverse order since they stack)
|
882
|
+
for deco in (
|
883
|
+
click.option(
|
884
|
+
"--pattern",
|
885
|
+
metavar="GLOB",
|
886
|
+
help="Apply to last --dir/--collect (replaces legacy --glob)",
|
887
|
+
),
|
888
|
+
click.option(
|
889
|
+
"--recursive",
|
890
|
+
is_flag=True,
|
891
|
+
help="Apply to last --dir/--collect",
|
892
|
+
),
|
893
|
+
click.option(
|
894
|
+
"-C",
|
895
|
+
"--collect",
|
896
|
+
"collects",
|
897
|
+
multiple=True,
|
898
|
+
nargs=2,
|
899
|
+
callback=validate_attachment_collect,
|
900
|
+
metavar="[TARGETS:]ALIAS @FILELIST",
|
901
|
+
help="Attach file collection: '[targets:]alias @file-list.txt'",
|
902
|
+
),
|
903
|
+
click.option(
|
904
|
+
"-D",
|
905
|
+
"--dir",
|
906
|
+
"dirs",
|
907
|
+
multiple=True,
|
908
|
+
nargs=2,
|
909
|
+
callback=validate_attachment_dir,
|
910
|
+
metavar="[TARGETS:]ALIAS PATH",
|
911
|
+
help="Attach directory: '[targets:]alias path'. Targets: prompt (default), code-interpreter/ci, file-search/fs",
|
912
|
+
),
|
913
|
+
click.option(
|
914
|
+
"-F",
|
915
|
+
"--file",
|
916
|
+
"attaches",
|
917
|
+
multiple=True,
|
918
|
+
nargs=2,
|
919
|
+
callback=validate_attachment_file,
|
920
|
+
metavar="[TARGETS:]ALIAS PATH",
|
921
|
+
help="Attach file: '[targets:]alias path'. Targets: prompt (default), code-interpreter/ci, file-search/fs",
|
922
|
+
),
|
923
|
+
):
|
924
|
+
cmd = deco(cmd)
|
925
|
+
|
926
|
+
# Apply the group decorator LAST so it sees all the options
|
927
|
+
cmd = cmd
|
928
|
+
|
929
|
+
return cast(Command, cmd)
|
930
|
+
|
931
|
+
|
861
932
|
def all_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
862
|
-
"""Apply all CLI options to a command.
|
933
|
+
"""Apply all CLI options to a command in progressive disclosure order.
|
934
|
+
|
935
|
+
Order: Essential → Core Workflow → Advanced Features → Debug/Development
|
936
|
+
"""
|
863
937
|
cmd: Any = f if isinstance(f, Command) else f
|
864
938
|
|
865
|
-
# Apply option groups in order
|
866
|
-
|
867
|
-
cmd =
|
868
|
-
cmd =
|
869
|
-
cmd =
|
870
|
-
|
871
|
-
|
872
|
-
cmd =
|
873
|
-
cmd = code_interpreter_options(cmd)
|
874
|
-
cmd = file_search_options(cmd)
|
939
|
+
# Apply option groups in progressive disclosure order (REVERSE order since they stack)
|
940
|
+
# Debug and Development Options (last - most advanced)
|
941
|
+
cmd = help_options(cmd)
|
942
|
+
cmd = debug_progress_options(cmd)
|
943
|
+
cmd = debug_options(cmd)
|
944
|
+
|
945
|
+
# Advanced Configuration Options
|
946
|
+
cmd = security_options(cmd) # Path security and allowlist options
|
875
947
|
cmd = web_search_options(cmd)
|
948
|
+
cmd = file_search_config_options(cmd) # File search config
|
949
|
+
cmd = feature_options(cmd) # Feature flags and config
|
950
|
+
cmd = mcp_options(cmd)
|
951
|
+
cmd = api_options(cmd)
|
952
|
+
|
953
|
+
# Core Workflow Options
|
954
|
+
cmd = output_options(cmd)
|
955
|
+
cmd = system_prompt_options(cmd)
|
876
956
|
cmd = tool_toggle_options(cmd)
|
877
|
-
cmd =
|
878
|
-
cmd =
|
957
|
+
cmd = file_options(cmd) # File attachment system
|
958
|
+
cmd = model_options(cmd)
|
959
|
+
|
960
|
+
# Essential Options (first - most important)
|
961
|
+
cmd = variable_options(cmd)
|
879
962
|
|
880
963
|
return cast(Command, cmd)
|
881
964
|
|