ostruct-cli 0.2.0__py3-none-any.whl → 0.4.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 +2 -2
- ostruct/cli/cli.py +466 -604
- ostruct/cli/click_options.py +257 -0
- ostruct/cli/errors.py +234 -183
- ostruct/cli/file_info.py +154 -50
- ostruct/cli/file_list.py +189 -64
- ostruct/cli/file_utils.py +95 -67
- ostruct/cli/path_utils.py +58 -77
- ostruct/cli/security/__init__.py +32 -0
- ostruct/cli/security/allowed_checker.py +47 -0
- ostruct/cli/security/case_manager.py +75 -0
- ostruct/cli/security/errors.py +184 -0
- ostruct/cli/security/normalization.py +161 -0
- ostruct/cli/security/safe_joiner.py +211 -0
- ostruct/cli/security/security_manager.py +353 -0
- ostruct/cli/security/symlink_resolver.py +483 -0
- ostruct/cli/security/types.py +108 -0
- ostruct/cli/security/windows_paths.py +404 -0
- ostruct/cli/template_filters.py +8 -5
- ostruct/cli/template_io.py +4 -2
- {ostruct_cli-0.2.0.dist-info → ostruct_cli-0.4.0.dist-info}/METADATA +9 -6
- ostruct_cli-0.4.0.dist-info/RECORD +36 -0
- ostruct/cli/security.py +0 -323
- ostruct/cli/security_types.py +0 -49
- ostruct_cli-0.2.0.dist-info/RECORD +0 -27
- {ostruct_cli-0.2.0.dist-info → ostruct_cli-0.4.0.dist-info}/LICENSE +0 -0
- {ostruct_cli-0.2.0.dist-info → ostruct_cli-0.4.0.dist-info}/WHEEL +0 -0
- {ostruct_cli-0.2.0.dist-info → ostruct_cli-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,257 @@
|
|
1
|
+
"""Click command and options for the CLI.
|
2
|
+
|
3
|
+
This module contains all Click-related code separated from the main CLI logic.
|
4
|
+
We isolate this code here and provide proper type annotations for Click's
|
5
|
+
decorator-based API.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Callable, TypeVar, Union, cast
|
9
|
+
|
10
|
+
import click
|
11
|
+
from click import Command
|
12
|
+
|
13
|
+
from ostruct import __version__
|
14
|
+
from ostruct.cli.errors import ( # noqa: F401 - Used in error handling
|
15
|
+
SystemPromptError,
|
16
|
+
TaskTemplateVariableError,
|
17
|
+
)
|
18
|
+
|
19
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
20
|
+
DecoratedCommand = Union[Command, Callable[..., Any]]
|
21
|
+
|
22
|
+
|
23
|
+
def validate_task_params(
|
24
|
+
ctx: click.Context, param: click.Parameter, value: Any
|
25
|
+
) -> Any:
|
26
|
+
"""Validate task-related parameters."""
|
27
|
+
if not hasattr(ctx, "params"):
|
28
|
+
return value
|
29
|
+
|
30
|
+
# Check for conflicting task parameters
|
31
|
+
if (
|
32
|
+
param.name == "task_file"
|
33
|
+
and value is not None
|
34
|
+
and ctx.params.get("task") is not None
|
35
|
+
):
|
36
|
+
raise click.UsageError("Cannot specify both --task and --task-file")
|
37
|
+
elif (
|
38
|
+
param.name == "task"
|
39
|
+
and value is not None
|
40
|
+
and ctx.params.get("task_file") is not None
|
41
|
+
):
|
42
|
+
raise click.UsageError("Cannot specify both --task and --task-file")
|
43
|
+
|
44
|
+
return value
|
45
|
+
|
46
|
+
|
47
|
+
def validate_system_prompt_params(
|
48
|
+
ctx: click.Context, param: click.Parameter, value: Any
|
49
|
+
) -> Any:
|
50
|
+
"""Validate system prompt parameters."""
|
51
|
+
if not hasattr(ctx, "params"):
|
52
|
+
return value
|
53
|
+
|
54
|
+
# Check for conflicting system prompt parameters
|
55
|
+
if (
|
56
|
+
param.name == "system_prompt_file"
|
57
|
+
and value is not None
|
58
|
+
and ctx.params.get("system_prompt") is not None
|
59
|
+
):
|
60
|
+
raise click.UsageError(
|
61
|
+
"Cannot specify both --system-prompt and --system-prompt-file"
|
62
|
+
)
|
63
|
+
elif (
|
64
|
+
param.name == "system_prompt"
|
65
|
+
and value is not None
|
66
|
+
and ctx.params.get("system_prompt_file") is not None
|
67
|
+
):
|
68
|
+
raise click.UsageError(
|
69
|
+
"Cannot specify both --system-prompt and --system-prompt-file"
|
70
|
+
)
|
71
|
+
|
72
|
+
return value
|
73
|
+
|
74
|
+
|
75
|
+
def debug_options(f: Callable) -> Callable:
|
76
|
+
"""Add debug-related CLI options."""
|
77
|
+
f = click.option(
|
78
|
+
"--show-model-schema",
|
79
|
+
is_flag=True,
|
80
|
+
help="Show generated Pydantic model schema",
|
81
|
+
)(f)
|
82
|
+
f = click.option(
|
83
|
+
"--debug-validation",
|
84
|
+
is_flag=True,
|
85
|
+
help="Show detailed validation errors",
|
86
|
+
)(f)
|
87
|
+
return f
|
88
|
+
|
89
|
+
|
90
|
+
def file_options(f: Callable) -> Callable:
|
91
|
+
"""Add file-related CLI options."""
|
92
|
+
f = click.option(
|
93
|
+
"--file", "-f", multiple=True, help="File mapping (name=path)"
|
94
|
+
)(f)
|
95
|
+
f = click.option(
|
96
|
+
"--files",
|
97
|
+
multiple=True,
|
98
|
+
help="Multiple file mappings from a directory",
|
99
|
+
)(f)
|
100
|
+
f = click.option(
|
101
|
+
"--dir", "-d", multiple=True, help="Directory mapping (name=path)"
|
102
|
+
)(f)
|
103
|
+
f = click.option(
|
104
|
+
"--allowed-dir",
|
105
|
+
multiple=True,
|
106
|
+
help="Additional allowed directory paths",
|
107
|
+
)(f)
|
108
|
+
f = click.option(
|
109
|
+
"--base-dir", type=str, help="Base directory for relative paths"
|
110
|
+
)(f)
|
111
|
+
f = click.option(
|
112
|
+
"--allowed-dir-file",
|
113
|
+
type=str,
|
114
|
+
help="File containing allowed directory paths",
|
115
|
+
)(f)
|
116
|
+
f = click.option(
|
117
|
+
"--dir-recursive", is_flag=True, help="Recursively process directories"
|
118
|
+
)(f)
|
119
|
+
f = click.option(
|
120
|
+
"--dir-ext", type=str, help="Filter directory files by extension"
|
121
|
+
)(f)
|
122
|
+
return f
|
123
|
+
|
124
|
+
|
125
|
+
def variable_options(f: Callable) -> Callable:
|
126
|
+
"""Add variable-related CLI options."""
|
127
|
+
f = click.option(
|
128
|
+
"--var", "-v", multiple=True, help="Variable mapping (name=value)"
|
129
|
+
)(f)
|
130
|
+
f = click.option(
|
131
|
+
"--json-var",
|
132
|
+
"-j",
|
133
|
+
multiple=True,
|
134
|
+
help="JSON variable mapping (name=json_value)",
|
135
|
+
)(f)
|
136
|
+
return f
|
137
|
+
|
138
|
+
|
139
|
+
def model_options(f: Callable) -> Callable:
|
140
|
+
"""Add model-related CLI options."""
|
141
|
+
f = click.option(
|
142
|
+
"--model", type=str, default="gpt-4o", help="OpenAI model to use"
|
143
|
+
)(f)
|
144
|
+
f = click.option(
|
145
|
+
"--temperature", type=float, default=0.0, help="Sampling temperature"
|
146
|
+
)(f)
|
147
|
+
f = click.option(
|
148
|
+
"--max-tokens", type=int, help="Maximum tokens in response"
|
149
|
+
)(f)
|
150
|
+
f = click.option(
|
151
|
+
"--top-p", type=float, default=1.0, help="Nucleus sampling threshold"
|
152
|
+
)(f)
|
153
|
+
f = click.option(
|
154
|
+
"--frequency-penalty",
|
155
|
+
type=float,
|
156
|
+
default=0.0,
|
157
|
+
help="Frequency penalty",
|
158
|
+
)(f)
|
159
|
+
f = click.option(
|
160
|
+
"--presence-penalty", type=float, default=0.0, help="Presence penalty"
|
161
|
+
)(f)
|
162
|
+
return f
|
163
|
+
|
164
|
+
|
165
|
+
def create_click_command() -> Callable[[F], Command]:
|
166
|
+
"""Create the Click command with all options.
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
A decorator function that adds all CLI options to the command.
|
170
|
+
"""
|
171
|
+
|
172
|
+
def decorator(f: F) -> Command:
|
173
|
+
# Start with the base command
|
174
|
+
cmd: DecoratedCommand = click.command(
|
175
|
+
help="Make structured OpenAI API calls."
|
176
|
+
)(f)
|
177
|
+
|
178
|
+
# Add all options
|
179
|
+
cmd = click.option(
|
180
|
+
"--task",
|
181
|
+
help="Task template string",
|
182
|
+
type=str,
|
183
|
+
callback=validate_task_params,
|
184
|
+
)(cmd)
|
185
|
+
cmd = click.option(
|
186
|
+
"--task-file",
|
187
|
+
help="Task template file path",
|
188
|
+
type=str,
|
189
|
+
callback=validate_task_params,
|
190
|
+
)(cmd)
|
191
|
+
cmd = click.option(
|
192
|
+
"--system-prompt",
|
193
|
+
help="System prompt string",
|
194
|
+
type=str,
|
195
|
+
callback=validate_system_prompt_params,
|
196
|
+
)(cmd)
|
197
|
+
cmd = click.option(
|
198
|
+
"--system-prompt-file",
|
199
|
+
help="System prompt file path",
|
200
|
+
type=str,
|
201
|
+
callback=validate_system_prompt_params,
|
202
|
+
)(cmd)
|
203
|
+
cmd = click.option(
|
204
|
+
"--schema-file",
|
205
|
+
required=True,
|
206
|
+
help="JSON schema file for response validation",
|
207
|
+
type=str,
|
208
|
+
)(cmd)
|
209
|
+
cmd = click.option(
|
210
|
+
"--ignore-task-sysprompt",
|
211
|
+
is_flag=True,
|
212
|
+
help="Ignore system prompt from task template YAML frontmatter",
|
213
|
+
)(cmd)
|
214
|
+
cmd = click.option(
|
215
|
+
"--timeout",
|
216
|
+
type=float,
|
217
|
+
default=60.0,
|
218
|
+
help="API timeout in seconds",
|
219
|
+
)(cmd)
|
220
|
+
cmd = click.option(
|
221
|
+
"--output-file", help="Write JSON output to file", type=str
|
222
|
+
)(cmd)
|
223
|
+
cmd = click.option(
|
224
|
+
"--dry-run",
|
225
|
+
is_flag=True,
|
226
|
+
help="Simulate API call without making request",
|
227
|
+
)(cmd)
|
228
|
+
cmd = click.option(
|
229
|
+
"--no-progress", is_flag=True, help="Disable progress indicators"
|
230
|
+
)(cmd)
|
231
|
+
cmd = click.option(
|
232
|
+
"--progress-level",
|
233
|
+
type=click.Choice(["none", "basic", "detailed"]),
|
234
|
+
default="basic",
|
235
|
+
help="Progress reporting level",
|
236
|
+
)(cmd)
|
237
|
+
cmd = click.option(
|
238
|
+
"--api-key", help="OpenAI API key (overrides env var)", type=str
|
239
|
+
)(cmd)
|
240
|
+
cmd = click.option(
|
241
|
+
"--verbose",
|
242
|
+
is_flag=True,
|
243
|
+
help="Enable verbose output and detailed logging",
|
244
|
+
)(cmd)
|
245
|
+
cmd = click.option(
|
246
|
+
"--debug-openai-stream",
|
247
|
+
is_flag=True,
|
248
|
+
help="Enable low-level debug output for OpenAI streaming",
|
249
|
+
)(cmd)
|
250
|
+
cmd = debug_options(cmd)
|
251
|
+
cmd = file_options(cmd)
|
252
|
+
cmd = variable_options(cmd)
|
253
|
+
cmd = model_options(cmd)
|
254
|
+
cmd = click.version_option(version=__version__)(cmd)
|
255
|
+
return cast(Command, cmd)
|
256
|
+
|
257
|
+
return decorator
|