ostruct-cli 0.3.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/cli.py +84 -118
- ostruct/cli/click_options.py +54 -45
- ostruct/cli/errors.py +63 -170
- ostruct/cli/file_info.py +98 -57
- ostruct/cli/file_list.py +189 -64
- ostruct/cli/file_utils.py +93 -66
- 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-0.3.0.dist-info → ostruct_cli-0.4.0.dist-info}/METADATA +6 -5
- ostruct_cli-0.4.0.dist-info/RECORD +36 -0
- ostruct/cli/security.py +0 -964
- ostruct/cli/security_types.py +0 -46
- ostruct_cli-0.3.0.dist-info/RECORD +0 -28
- {ostruct_cli-0.3.0.dist-info → ostruct_cli-0.4.0.dist-info}/LICENSE +0 -0
- {ostruct_cli-0.3.0.dist-info → ostruct_cli-0.4.0.dist-info}/WHEEL +0 -0
- {ostruct_cli-0.3.0.dist-info → ostruct_cli-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,404 @@
|
|
1
|
+
"""Windows path handling and validation.
|
2
|
+
|
3
|
+
This module provides functions for handling Windows-specific path features:
|
4
|
+
- Device paths (r"\\\\?\\", r"\\\\.")
|
5
|
+
- Drive-relative paths (C:folder)
|
6
|
+
- Reserved names (CON, PRN, etc.)
|
7
|
+
- UNC paths (r"\\\\server\\share")
|
8
|
+
- Alternate Data Streams (file.txt:stream)
|
9
|
+
|
10
|
+
Security Design Choices:
|
11
|
+
1. Device Paths:
|
12
|
+
- Explicitly blocked for security
|
13
|
+
- No support for extended-length paths
|
14
|
+
- No direct device access allowed
|
15
|
+
|
16
|
+
2. Drive Paths:
|
17
|
+
- Drive-relative paths must include separator
|
18
|
+
- Drive absolute paths are allowed
|
19
|
+
- Drive letters must be A-Z (case insensitive)
|
20
|
+
|
21
|
+
3. Reserved Names:
|
22
|
+
- All Windows reserved names blocked
|
23
|
+
- Case-insensitive matching
|
24
|
+
- Blocked with or without extensions
|
25
|
+
|
26
|
+
4. UNC Paths:
|
27
|
+
- Must be complete (server and share)
|
28
|
+
- No device paths in UNC format
|
29
|
+
- Normalized to forward slashes
|
30
|
+
|
31
|
+
5. Alternate Data Streams:
|
32
|
+
- All ADS access is blocked
|
33
|
+
- No exceptions for Zone.Identifier
|
34
|
+
- Blocks both read and write
|
35
|
+
|
36
|
+
Known Limitations:
|
37
|
+
1. Path Length:
|
38
|
+
- No extended-length path support
|
39
|
+
- Standard Windows MAX_PATH limits
|
40
|
+
- No workarounds for long paths
|
41
|
+
|
42
|
+
2. Network:
|
43
|
+
- No special handling for DFS
|
44
|
+
- No support for administrative shares
|
45
|
+
- Basic UNC validation only
|
46
|
+
|
47
|
+
3. Security:
|
48
|
+
- Some rare path formats may bypass checks
|
49
|
+
- Complex NTFS features not handled
|
50
|
+
- Limited reparse point support
|
51
|
+
"""
|
52
|
+
|
53
|
+
import logging
|
54
|
+
import os
|
55
|
+
import re
|
56
|
+
from pathlib import Path, WindowsPath
|
57
|
+
from typing import Optional, Union
|
58
|
+
|
59
|
+
from .errors import PathSecurityError, SecurityErrorReasons
|
60
|
+
|
61
|
+
logger = logging.getLogger(__name__)
|
62
|
+
|
63
|
+
# Windows path length limits
|
64
|
+
MAX_PATH = 260
|
65
|
+
EXTENDED_MAX_PATH = 32767
|
66
|
+
|
67
|
+
# Regex patterns for Windows path features
|
68
|
+
_WINDOWS_DEVICE_PATH = re.compile(
|
69
|
+
r"^(?:\\\\|//)[?.](?:\\|/)(?!UNC(?:\\|/))", # Match device paths but exclude UNC
|
70
|
+
flags=re.IGNORECASE,
|
71
|
+
)
|
72
|
+
|
73
|
+
_WINDOWS_DRIVE_RELATIVE = re.compile(
|
74
|
+
r"(?:^|[/\\])[A-Za-z]:(?![/\\])|" # C:folder or \C:folder but not C:\folder
|
75
|
+
r"^/[A-Za-z]:(?![/\\])" # /C:folder variants
|
76
|
+
)
|
77
|
+
|
78
|
+
_WINDOWS_RESERVED_NAMES = re.compile(
|
79
|
+
r"^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])" # Base names
|
80
|
+
r"(\.[^\\/:*?\"<>|]*)?$", # Optional extension
|
81
|
+
re.IGNORECASE,
|
82
|
+
)
|
83
|
+
|
84
|
+
_WINDOWS_UNC = re.compile(
|
85
|
+
r"^\\\\[^?.\\/][^\\/]*\\[^\\/]+(?:\\.*)?|" # \\server\share[\anything]
|
86
|
+
r"^//[^?./][^/]*/[^/]+(?:/.*)?$" # //server/share[/anything]
|
87
|
+
)
|
88
|
+
|
89
|
+
_WINDOWS_INCOMPLETE_UNC = re.compile(
|
90
|
+
r"^\\\\[^?.\\/][^\\/]*(?:\\[^\\/]+)?$|" # \\server or \\server\incomplete
|
91
|
+
r"^//[^?./][^/]*(?:/[^/]+)?$" # //server or //server/incomplete variants
|
92
|
+
)
|
93
|
+
|
94
|
+
_WINDOWS_ADS = re.compile(
|
95
|
+
r":[^/\\<>:\"|?*]+$|" # Basic ADS
|
96
|
+
r":Zone\.Identifier$|" # Zone.Identifier
|
97
|
+
r":[^/\\<>:\"|?*]+:[^/\\]+$" # Multiple stream segments
|
98
|
+
)
|
99
|
+
|
100
|
+
_WINDOWS_INVALID_CHARS = re.compile(
|
101
|
+
r'[<>"|?*]|' # Standard invalid chars except colon
|
102
|
+
r"(?<!^[A-Za-z]):|" # Colon except after drive letter at start
|
103
|
+
r"[\x00-\x1F]" # Control chars
|
104
|
+
)
|
105
|
+
|
106
|
+
_WINDOWS_TRAILING = re.compile(r"[. ]+$") # Trailing dots/spaces
|
107
|
+
|
108
|
+
|
109
|
+
def is_windows_path(path: Union[str, Path]) -> bool:
|
110
|
+
"""Check if path uses Windows-specific features.
|
111
|
+
|
112
|
+
Security Note:
|
113
|
+
- Detects device paths (r"\\?\\" and r"\\.\\") in both slash formats
|
114
|
+
- Case insensitive to handle drive letters
|
115
|
+
"""
|
116
|
+
path_str = str(path)
|
117
|
+
|
118
|
+
# Normalize slashes for consistent matching
|
119
|
+
normalized_path = path_str.replace("\\", "/")
|
120
|
+
|
121
|
+
# Check for device paths first before any processing
|
122
|
+
if _WINDOWS_DEVICE_PATH.match(path_str) or _WINDOWS_DEVICE_PATH.match(
|
123
|
+
normalized_path
|
124
|
+
):
|
125
|
+
logger.debug("Windows device path detected: %r", path_str)
|
126
|
+
return True
|
127
|
+
|
128
|
+
# Rest of the function remains unchanged
|
129
|
+
basename = os.path.basename(path_str)
|
130
|
+
is_drive_relative = bool(_WINDOWS_DRIVE_RELATIVE.search(path_str))
|
131
|
+
is_unc = bool(_WINDOWS_UNC.search(path_str))
|
132
|
+
is_ads = bool(_WINDOWS_ADS.search(path_str))
|
133
|
+
is_reserved = bool(_WINDOWS_RESERVED_NAMES.match(basename))
|
134
|
+
|
135
|
+
logger.debug(
|
136
|
+
"Windows path check for %r: drive_relative=%s, unc=%s, ads=%s, reserved=%s",
|
137
|
+
path_str,
|
138
|
+
is_drive_relative,
|
139
|
+
is_unc,
|
140
|
+
is_ads,
|
141
|
+
is_reserved,
|
142
|
+
)
|
143
|
+
|
144
|
+
return bool(is_drive_relative or is_unc or is_ads or is_reserved)
|
145
|
+
|
146
|
+
|
147
|
+
def normalize_windows_path(path: Union[str, Path]) -> Path:
|
148
|
+
"""Normalize a path using Windows-specific rules.
|
149
|
+
|
150
|
+
This function:
|
151
|
+
1. Converts to Path with Windows semantics
|
152
|
+
2. Resolves to absolute path
|
153
|
+
3. Normalizes separators and case
|
154
|
+
4. Removes redundant separators and dots
|
155
|
+
|
156
|
+
Args:
|
157
|
+
path: Path to normalize
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
Normalized Path
|
161
|
+
|
162
|
+
Raises:
|
163
|
+
PathSecurityError: If path cannot be normalized
|
164
|
+
"""
|
165
|
+
try:
|
166
|
+
logger.debug("Normalizing Windows path: %r", path)
|
167
|
+
|
168
|
+
# Convert to string and normalize all slashes to forward slashes first
|
169
|
+
path_str = str(path)
|
170
|
+
# Replace all backslashes with forward slashes for consistent handling
|
171
|
+
path_str = path_str.replace("\\", "/")
|
172
|
+
# Collapse multiple slashes to single slash, except for UNC prefixes
|
173
|
+
path_str = re.sub(r"(?<!^)//+", "/", path_str)
|
174
|
+
logger.debug("Normalized slashes: %r", path_str)
|
175
|
+
|
176
|
+
# Use regular Path on non-Windows systems
|
177
|
+
path_cls = WindowsPath if os.name == "nt" else Path
|
178
|
+
|
179
|
+
# Convert back to backslashes for Windows path handling
|
180
|
+
if os.name == "nt":
|
181
|
+
path_str = path_str.replace("/", "\\")
|
182
|
+
# Preserve UNC path double backslashes
|
183
|
+
if path_str.startswith("\\") and not path_str.startswith("\\\\"):
|
184
|
+
path_str = "\\" + path_str
|
185
|
+
|
186
|
+
normalized = path_cls(path_str)
|
187
|
+
logger.debug(
|
188
|
+
"Created path object: %r (class=%s)", normalized, path_cls.__name__
|
189
|
+
)
|
190
|
+
|
191
|
+
if os.name == "nt":
|
192
|
+
# Check if resolve() would exceed MAX_PATH
|
193
|
+
resolved = normalized.resolve()
|
194
|
+
resolved_str = str(resolved)
|
195
|
+
# If resolve() added \\?\ prefix or path is too long, reject it
|
196
|
+
if (
|
197
|
+
resolved_str.startswith("\\\\?\\")
|
198
|
+
or len(resolved_str) > MAX_PATH
|
199
|
+
):
|
200
|
+
raise PathSecurityError(
|
201
|
+
f"Path would exceed maximum length of {MAX_PATH} characters after resolution",
|
202
|
+
path=str(path),
|
203
|
+
context={
|
204
|
+
"reason": SecurityErrorReasons.NORMALIZATION_ERROR
|
205
|
+
},
|
206
|
+
)
|
207
|
+
normalized = resolved
|
208
|
+
logger.debug("Resolved on Windows: %r", normalized)
|
209
|
+
else:
|
210
|
+
# On non-Windows, just normalize the path
|
211
|
+
normalized = Path(os.path.normpath(path_str))
|
212
|
+
logger.debug("Normalized on non-Windows: %r", normalized)
|
213
|
+
|
214
|
+
return normalized
|
215
|
+
except PathSecurityError:
|
216
|
+
raise
|
217
|
+
except Exception as e:
|
218
|
+
logger.error(
|
219
|
+
"Failed to normalize Windows path %r: %s", path, e, exc_info=True
|
220
|
+
)
|
221
|
+
raise PathSecurityError(
|
222
|
+
f"Failed to normalize Windows path: {e}",
|
223
|
+
path=str(path),
|
224
|
+
context={"reason": SecurityErrorReasons.NORMALIZATION_ERROR},
|
225
|
+
)
|
226
|
+
|
227
|
+
|
228
|
+
def validate_windows_path(path: Union[str, Path]) -> Optional[str]:
|
229
|
+
"""Validate a path for Windows-specific security issues.
|
230
|
+
|
231
|
+
Performs checks in order:
|
232
|
+
1. Device paths (blocked)
|
233
|
+
2. Path normalization
|
234
|
+
3. Other Windows-specific checks
|
235
|
+
|
236
|
+
Returns an error message if the path:
|
237
|
+
- Uses device paths (r"\\\\?\\", r"\\\\.")
|
238
|
+
- Uses drive-relative paths (C:folder)
|
239
|
+
- Contains reserved names (CON, PRN, etc.)
|
240
|
+
- Uses UNC paths (r"\\\\server\\share")
|
241
|
+
- Contains Alternate Data Streams (file.txt:stream)
|
242
|
+
- Exceeds maximum path length
|
243
|
+
- Contains invalid characters
|
244
|
+
- Has trailing dots or spaces
|
245
|
+
|
246
|
+
Returns None if the path is valid.
|
247
|
+
"""
|
248
|
+
logger.debug("Validating Windows path: %r", path)
|
249
|
+
|
250
|
+
# Initial checks on raw path string
|
251
|
+
path_str = str(path)
|
252
|
+
|
253
|
+
# Normalize slashes for consistent matching
|
254
|
+
normalized_path = path_str.replace("\\", "/")
|
255
|
+
|
256
|
+
# Check for device paths before any processing
|
257
|
+
if _WINDOWS_DEVICE_PATH.match(path_str) or _WINDOWS_DEVICE_PATH.match(
|
258
|
+
normalized_path
|
259
|
+
):
|
260
|
+
logger.debug("Device path detected in original path: %r", path_str)
|
261
|
+
return "Device paths not allowed"
|
262
|
+
|
263
|
+
# Check for incomplete UNC paths before normalization
|
264
|
+
if _WINDOWS_INCOMPLETE_UNC.search(path_str):
|
265
|
+
logger.debug("Incomplete UNC path detected: %r", path_str)
|
266
|
+
return "Incomplete UNC path"
|
267
|
+
|
268
|
+
# Then normalize the path for other checks
|
269
|
+
try:
|
270
|
+
normalized_str = str(normalize_windows_path(path))
|
271
|
+
logger.debug("Normalized path: %r", normalized_str)
|
272
|
+
|
273
|
+
# Check for device paths again after normalization
|
274
|
+
if _WINDOWS_DEVICE_PATH.match(normalized_str):
|
275
|
+
logger.debug(
|
276
|
+
"Device path detected after normalization: %r", normalized_str
|
277
|
+
)
|
278
|
+
return "Device paths not allowed"
|
279
|
+
|
280
|
+
except PathSecurityError as e:
|
281
|
+
logger.debug("Path normalization failed: %s", e)
|
282
|
+
return str(e)
|
283
|
+
|
284
|
+
# Check path length
|
285
|
+
if len(normalized_str) > MAX_PATH:
|
286
|
+
msg = f"Path exceeds maximum length of {MAX_PATH} characters"
|
287
|
+
logger.debug("Path too long: %s", msg)
|
288
|
+
return msg
|
289
|
+
|
290
|
+
if _WINDOWS_DRIVE_RELATIVE.search(normalized_str):
|
291
|
+
logger.debug("Drive-relative path detected: %r", normalized_str)
|
292
|
+
return "Drive-relative paths must include separator"
|
293
|
+
|
294
|
+
# Check for complete UNC paths
|
295
|
+
if _WINDOWS_UNC.search(normalized_str):
|
296
|
+
logger.debug("UNC path detected: %r", normalized_str)
|
297
|
+
return "UNC paths not allowed"
|
298
|
+
|
299
|
+
if _WINDOWS_ADS.search(normalized_str):
|
300
|
+
logger.debug("Alternate Data Stream detected: %r", normalized_str)
|
301
|
+
return "Alternate Data Streams not allowed"
|
302
|
+
|
303
|
+
# Check each path component
|
304
|
+
try:
|
305
|
+
parts = (
|
306
|
+
Path(normalized_str).parts
|
307
|
+
if os.name != "nt"
|
308
|
+
else WindowsPath(normalized_str).parts
|
309
|
+
)
|
310
|
+
logger.debug("Path components: %r", parts)
|
311
|
+
|
312
|
+
for part in parts:
|
313
|
+
# Check for reserved names
|
314
|
+
if _WINDOWS_RESERVED_NAMES.match(part):
|
315
|
+
logger.debug("Reserved name detected: %r", part)
|
316
|
+
return "Windows reserved names not allowed"
|
317
|
+
|
318
|
+
# Check for invalid characters
|
319
|
+
if _WINDOWS_INVALID_CHARS.search(part):
|
320
|
+
msg = f"Invalid characters in path component '{part}'"
|
321
|
+
logger.debug("Invalid characters: %s", msg)
|
322
|
+
return msg
|
323
|
+
|
324
|
+
# Check for trailing dots/spaces
|
325
|
+
if _WINDOWS_TRAILING.search(part):
|
326
|
+
msg = f"Trailing dots or spaces not allowed in '{part}'"
|
327
|
+
logger.debug("Trailing dots/spaces: %s", msg)
|
328
|
+
return msg
|
329
|
+
except Exception as e:
|
330
|
+
logger.error("Failed to check path components: %s", e, exc_info=True)
|
331
|
+
return f"Failed to validate path components: {e}"
|
332
|
+
|
333
|
+
logger.debug("Path validation successful: %r", normalized_str)
|
334
|
+
return None
|
335
|
+
|
336
|
+
|
337
|
+
def resolve_windows_symlink(path: Path) -> Optional[Path]:
|
338
|
+
"""Resolve a Windows symlink or reparse point.
|
339
|
+
|
340
|
+
This is a Windows-specific helper for symlink resolution that handles:
|
341
|
+
- NTFS symbolic links
|
342
|
+
- NTFS junction points
|
343
|
+
- NTFS mount points
|
344
|
+
- Other reparse points
|
345
|
+
|
346
|
+
Args:
|
347
|
+
path: The path to resolve.
|
348
|
+
|
349
|
+
Returns:
|
350
|
+
Resolved Path if successful, None if not a Windows symlink.
|
351
|
+
|
352
|
+
Note:
|
353
|
+
This function requires Windows and elevated privileges for some
|
354
|
+
reparse point operations.
|
355
|
+
|
356
|
+
Security Note:
|
357
|
+
By default, this function only handles regular symlinks.
|
358
|
+
For security reasons, other reparse points (junctions, mount points)
|
359
|
+
are not resolved by default as they can bypass directory restrictions.
|
360
|
+
If you need to handle these, implement proper security checks in the
|
361
|
+
calling code.
|
362
|
+
"""
|
363
|
+
if os.name != "nt":
|
364
|
+
return None
|
365
|
+
|
366
|
+
try:
|
367
|
+
# Try to resolve as a regular symlink first
|
368
|
+
if path.is_symlink():
|
369
|
+
target = Path(os.readlink(path))
|
370
|
+
logger.debug("Resolved symlink %r to %r", path, target)
|
371
|
+
return target
|
372
|
+
|
373
|
+
# Check if it's a reparse point but not a symlink
|
374
|
+
# This requires using Windows APIs, so we just warn about it
|
375
|
+
if hasattr(path, "is_mount") and path.is_mount():
|
376
|
+
logger.warning(
|
377
|
+
"Path %r is a mount point/junction - not resolving for security",
|
378
|
+
path,
|
379
|
+
)
|
380
|
+
return None
|
381
|
+
|
382
|
+
# For any other reparse points, log a warning
|
383
|
+
try:
|
384
|
+
import ctypes
|
385
|
+
|
386
|
+
attrs = ctypes.windll.kernel32.GetFileAttributesW(str(path)) # type: ignore[attr-defined]
|
387
|
+
is_reparse = bool(
|
388
|
+
attrs != -1 and attrs & 0x400
|
389
|
+
) # FILE_ATTRIBUTE_REPARSE_POINT
|
390
|
+
if is_reparse:
|
391
|
+
logger.warning(
|
392
|
+
"Path %r is a reparse point - not resolving for security",
|
393
|
+
path,
|
394
|
+
)
|
395
|
+
return None
|
396
|
+
except Exception:
|
397
|
+
# If we can't check reparse attributes, assume it's not a reparse point
|
398
|
+
pass
|
399
|
+
|
400
|
+
return None
|
401
|
+
|
402
|
+
except OSError as e:
|
403
|
+
logger.debug("Failed to resolve Windows symlink %r: %s", path, e)
|
404
|
+
return None
|
ostruct/cli/template_filters.py
CHANGED
@@ -544,9 +544,9 @@ def format_code(
|
|
544
544
|
"""Format code with syntax highlighting.
|
545
545
|
|
546
546
|
Args:
|
547
|
-
text
|
548
|
-
output_format
|
549
|
-
language
|
547
|
+
text: The code text to format
|
548
|
+
output_format: The output format ('terminal', 'html', or 'plain')
|
549
|
+
language: The programming language for syntax highlighting
|
550
550
|
|
551
551
|
Returns:
|
552
552
|
str: Formatted code string
|
@@ -580,10 +580,13 @@ def format_code(
|
|
580
580
|
else: # plain
|
581
581
|
formatter = NullFormatter[str]()
|
582
582
|
|
583
|
-
|
583
|
+
result = highlight(text, lexer, formatter)
|
584
|
+
if isinstance(result, bytes):
|
585
|
+
return result.decode("utf-8")
|
586
|
+
return str(result)
|
584
587
|
except Exception as e:
|
585
588
|
logger.error(f"Error formatting code: {e}")
|
586
|
-
return text
|
589
|
+
return str(text)
|
587
590
|
|
588
591
|
|
589
592
|
def register_template_filters(env: Environment) -> None:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ostruct-cli
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: CLI for OpenAI Structured Output
|
5
5
|
Author: Yaniv Golan
|
6
6
|
Author-email: yaniv@golan.name
|
@@ -16,7 +16,8 @@ Requires-Dist: chardet (>=5.0.0,<6.0.0)
|
|
16
16
|
Requires-Dist: click (>=8.1.7,<9.0.0)
|
17
17
|
Requires-Dist: ijson (>=3.2.3,<4.0.0)
|
18
18
|
Requires-Dist: jsonschema (>=4.23.0,<5.0.0)
|
19
|
-
Requires-Dist: openai
|
19
|
+
Requires-Dist: openai (>=1.0.0,<2.0.0)
|
20
|
+
Requires-Dist: openai-structured (>=1.3.0,<2.0.0)
|
20
21
|
Requires-Dist: pydantic (>=2.6.3,<3.0.0)
|
21
22
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
22
23
|
Requires-Dist: tiktoken (>=0.8.0,<0.9.0)
|
@@ -28,8 +29,8 @@ Description-Content-Type: text/markdown
|
|
28
29
|
# ostruct-cli
|
29
30
|
|
30
31
|
[](https://badge.fury.io/py/ostruct-cli)
|
31
|
-
[](https://pypi.org/project/ostruct-cli
|
32
|
-
[](https://pypi.org/project/ostruct-cli)
|
33
|
+
[](https://ostruct.readthedocs.io/en/latest/?badge=latest)
|
33
34
|
[](https://github.com/yaniv-golan/ostruct/actions/workflows/ci.yml)
|
34
35
|
[](https://opensource.org/licenses/MIT)
|
35
36
|
|
@@ -126,7 +127,7 @@ All debug and error logs are written to:
|
|
126
127
|
- `~/.ostruct/logs/ostruct.log`: General application logs
|
127
128
|
- `~/.ostruct/logs/openai_stream.log`: OpenAI streaming operations logs
|
128
129
|
|
129
|
-
For more detailed documentation and examples, visit our [documentation](https://ostruct
|
130
|
+
For more detailed documentation and examples, visit our [documentation](https://ostruct.readthedocs.io/).
|
130
131
|
|
131
132
|
## Development
|
132
133
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
ostruct/__init__.py,sha256=X6zo6V7ZNMv731Wi388aTVQngD1410ExGwGx4J6lpyo,187
|
2
|
+
ostruct/cli/__init__.py,sha256=sYHKT6o1kFy1acbXejzAvVm8Cy8U91Yf1l4DlzquHKg,409
|
3
|
+
ostruct/cli/cache_manager.py,sha256=ej3KrRfkKKZ_lEp2JswjbJ5bW2ncsvna9NeJu81cqqs,5192
|
4
|
+
ostruct/cli/cli.py,sha256=kh2_P8O7BAbu7qWB64d1qH0rgWSJZuyxn4MdXd13h4I,65574
|
5
|
+
ostruct/cli/click_options.py,sha256=rrx04tiZmOu2103k0WJMNtc4ZYGpDftD9DjtGCVaNW0,7551
|
6
|
+
ostruct/cli/errors.py,sha256=lr8c4nkRCZZ5QoWHMZxX4B22CNoaoL1JJx8Y8WTt53Y,10448
|
7
|
+
ostruct/cli/file_info.py,sha256=xafeONqhUC7D9OPVuDIlc-44KcrPjdEBVmHN69CEtzs,13838
|
8
|
+
ostruct/cli/file_list.py,sha256=OiOl0NfkrWipZEdtRAf4eDJGDo2kyWdurxmU2k4KaZ0,11438
|
9
|
+
ostruct/cli/file_utils.py,sha256=On5zjqTx0qKlcZBbt1SNaq7HXtEON5_vyeTFE4UsCFg,22075
|
10
|
+
ostruct/cli/path_utils.py,sha256=RzGO-QOrp__NtDcIfAjfKl71NSXz3m_pb07UybgF8ro,3681
|
11
|
+
ostruct/cli/progress.py,sha256=rj9nVEco5UeZORMbzd7mFJpFGJjbH9KbBFh5oTE5Anw,3415
|
12
|
+
ostruct/cli/security/__init__.py,sha256=CQpkCgTFYlA1p6atpQeNgIKtE4LZGUKt4EbytbGKpCs,846
|
13
|
+
ostruct/cli/security/allowed_checker.py,sha256=y_R1UIJeGr1Ah1jlsg8t6aO28DnOfLqSH0wqmlVhx5A,1369
|
14
|
+
ostruct/cli/security/case_manager.py,sha256=I_ZJSyntLuGx5qVzze559CI-OxsaNPSibkAN8zZ7PvE,2345
|
15
|
+
ostruct/cli/security/errors.py,sha256=hDYKxo7V600ibXtXbrDoCbLNL7MEkarZEQbO2QYfTnI,5859
|
16
|
+
ostruct/cli/security/normalization.py,sha256=qevvxW3hHDtD1cVvDym8LJEQD1AKenVB-0ZvjCYjn5E,5242
|
17
|
+
ostruct/cli/security/safe_joiner.py,sha256=PHowCeBAkfHfPqRwuO5Com0OemGuq3cHkdu2p9IYNT0,7107
|
18
|
+
ostruct/cli/security/security_manager.py,sha256=KkI-fApKoDfRD7HSlz_H5LrIMbM5Rz9aP727t4Q_b5g,12770
|
19
|
+
ostruct/cli/security/symlink_resolver.py,sha256=wtZdJ_T_0FOy6B1P5ty1odEXQk9vr8BzlWeAFD4huJE,16744
|
20
|
+
ostruct/cli/security/types.py,sha256=15yuG_T4CXyAFFFdSWLjVS7ACmDGIPXhQpZ8awcDwCQ,2991
|
21
|
+
ostruct/cli/security/windows_paths.py,sha256=qxC2H2kLwtmQ7YePYde3UrmOJcGnsLEebDLh242sUaI,13453
|
22
|
+
ostruct/cli/template_env.py,sha256=S2ZvxuMQMicodSVqUhrw0kOzbNmlpQjSHtWlOwjXCms,1538
|
23
|
+
ostruct/cli/template_extensions.py,sha256=tJN3HGAS2yzGI8Up6STPday8NVL0VV6UCClBrtDKYr0,1623
|
24
|
+
ostruct/cli/template_filters.py,sha256=wZiR08e2_2SW28B7_tTU3wiij_KTCx3CCvlg-P2q7mk,19126
|
25
|
+
ostruct/cli/template_io.py,sha256=yUWO-8rZnSdX97DTMSEX8fG9CP1ISsOhm2NZN3Fab9A,8821
|
26
|
+
ostruct/cli/template_rendering.py,sha256=GrQAcKpGe6QEjSVQkOjpegMcor9LzVUikGmmEVgiWCE,12391
|
27
|
+
ostruct/cli/template_schema.py,sha256=ckH4rUZnEgfm_BHS9LnMGr8LtDxRmZ0C6UBVrSp8KTc,19604
|
28
|
+
ostruct/cli/template_utils.py,sha256=QGgewxU_Tgn81J5U-Y4xfi67CkN2dEqXI7PsaNiI9es,7812
|
29
|
+
ostruct/cli/template_validation.py,sha256=q3ACw4TscdekJb3Z3CTYw0YPEYttqjKjm74ap4lWtU4,11737
|
30
|
+
ostruct/cli/utils.py,sha256=1UCl4rHjBWKR5EKugvlVGHiHjO3XXmqvkgeAUSyIPDU,831
|
31
|
+
ostruct/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
+
ostruct_cli-0.4.0.dist-info/LICENSE,sha256=QUOY6QCYVxAiH8vdrUTDqe3i9hQ5bcNczppDSVpLTjk,1068
|
33
|
+
ostruct_cli-0.4.0.dist-info/METADATA,sha256=Khg3pmpYFixNMW_RrpF9bfD_ZRJDjAqP4VDWDdMcBhY,5405
|
34
|
+
ostruct_cli-0.4.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
35
|
+
ostruct_cli-0.4.0.dist-info/entry_points.txt,sha256=NFq9IuqHVTem0j9zKjV8C1si_zGcP1RL6Wbvt9fUDXw,48
|
36
|
+
ostruct_cli-0.4.0.dist-info/RECORD,,
|