scry-run 0.1.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.
- scry_run/__init__.py +102 -0
- scry_run/backends/__init__.py +6 -0
- scry_run/backends/base.py +65 -0
- scry_run/backends/claude.py +404 -0
- scry_run/backends/frozen.py +85 -0
- scry_run/backends/registry.py +72 -0
- scry_run/cache.py +441 -0
- scry_run/cli/__init__.py +137 -0
- scry_run/cli/apps.py +396 -0
- scry_run/cli/cache.py +342 -0
- scry_run/cli/config_cmd.py +84 -0
- scry_run/cli/env.py +27 -0
- scry_run/cli/init.py +375 -0
- scry_run/cli/run.py +71 -0
- scry_run/config.py +141 -0
- scry_run/console.py +52 -0
- scry_run/context.py +298 -0
- scry_run/generator.py +698 -0
- scry_run/home.py +60 -0
- scry_run/logging.py +171 -0
- scry_run/meta.py +1852 -0
- scry_run/packages.py +175 -0
- scry_run-0.1.0.dist-info/METADATA +282 -0
- scry_run-0.1.0.dist-info/RECORD +26 -0
- scry_run-0.1.0.dist-info/WHEEL +4 -0
- scry_run-0.1.0.dist-info/entry_points.txt +2 -0
scry_run/home.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Home directory management for scry-run.
|
|
2
|
+
|
|
3
|
+
All paths are resolved through this module. Override with SCRY_HOME env var.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_home() -> Path:
|
|
11
|
+
"""Get scry-run home directory.
|
|
12
|
+
|
|
13
|
+
Resolution order:
|
|
14
|
+
1. SCRY_HOME env var (for tests/custom setups)
|
|
15
|
+
2. ~/.scry-run/
|
|
16
|
+
"""
|
|
17
|
+
if env_home := os.environ.get("SCRY_HOME"):
|
|
18
|
+
return Path(env_home)
|
|
19
|
+
return Path.home() / ".scry-run"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_config_path() -> Path:
|
|
23
|
+
"""Get path to global config file."""
|
|
24
|
+
return get_home() / "config.toml"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_apps_dir() -> Path:
|
|
28
|
+
"""Get path to apps directory."""
|
|
29
|
+
return get_home() / "apps"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_app_dir(app_name: str) -> Path:
|
|
33
|
+
"""Get path to a specific app's directory."""
|
|
34
|
+
return get_apps_dir() / app_name
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_app_cache(app_name: str) -> Path:
|
|
38
|
+
"""Get path to an app's cache file."""
|
|
39
|
+
return get_app_dir(app_name) / "cache.json"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_app_logs(app_name: str) -> Path:
|
|
43
|
+
"""Get path to an app's logs directory."""
|
|
44
|
+
return get_app_dir(app_name) / "logs"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def ensure_home_exists() -> Path:
|
|
48
|
+
"""Ensure home directory structure exists.
|
|
49
|
+
|
|
50
|
+
Creates:
|
|
51
|
+
- ~/.scry-run/
|
|
52
|
+
- ~/.scry-run/apps/
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Path to home directory
|
|
56
|
+
"""
|
|
57
|
+
home = get_home()
|
|
58
|
+
home.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
(home / "apps").mkdir(exist_ok=True)
|
|
60
|
+
return home
|
scry_run/logging.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Logging utilities for scry-run.
|
|
2
|
+
|
|
3
|
+
Provides detailed file-based logging for debugging generation issues.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import traceback
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ScryRunLogger:
|
|
15
|
+
"""Logger that writes detailed debug info to a file.
|
|
16
|
+
|
|
17
|
+
Log file is created in:
|
|
18
|
+
1. The app's logs/ directory (if running in an app context)
|
|
19
|
+
2. Directory specified by SCRY_LOG_DIR
|
|
20
|
+
3. Current working directory (fallback)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
_instance: Optional["ScryRunLogger"] = None
|
|
24
|
+
|
|
25
|
+
def __init__(self, log_dir: Optional[Path] = None):
|
|
26
|
+
"""Initialize logger.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
log_dir: Directory for log file. If None, uses auto-detection.
|
|
30
|
+
"""
|
|
31
|
+
self._log_dir: Optional[Path] = None
|
|
32
|
+
self._log_file: Optional[Path] = None
|
|
33
|
+
# Session timestamp for unique log files per run
|
|
34
|
+
self._session_id = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
35
|
+
|
|
36
|
+
if log_dir:
|
|
37
|
+
self._log_dir = log_dir
|
|
38
|
+
else:
|
|
39
|
+
env_dir = os.environ.get("SCRY_LOG_DIR")
|
|
40
|
+
if env_dir:
|
|
41
|
+
self._log_dir = Path(env_dir)
|
|
42
|
+
|
|
43
|
+
# Debug logging is ON by default. Set SCRY_DEBUG=0 to disable.
|
|
44
|
+
debug_env = os.environ.get("SCRY_DEBUG", "").lower()
|
|
45
|
+
self._enabled = debug_env not in ("0", "false", "no", "off")
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def log_dir(self) -> Path:
|
|
49
|
+
"""Get log directory, with fallback to cwd."""
|
|
50
|
+
if self._log_dir:
|
|
51
|
+
return self._log_dir
|
|
52
|
+
return Path.cwd()
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def log_file(self) -> Path:
|
|
56
|
+
"""Get log file path (unique per session)."""
|
|
57
|
+
if self._log_file:
|
|
58
|
+
return self._log_file
|
|
59
|
+
return self.log_dir / f"generation_{self._session_id}.log"
|
|
60
|
+
|
|
61
|
+
def set_log_dir(self, log_dir: Path):
|
|
62
|
+
"""Set the log directory to use.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
log_dir: Directory for log files
|
|
66
|
+
"""
|
|
67
|
+
self._log_dir = log_dir
|
|
68
|
+
self._log_file = log_dir / f"generation_{self._session_id}.log"
|
|
69
|
+
# Ensure directory exists
|
|
70
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
def set_app_context(self, app_dir: Path):
|
|
73
|
+
"""Set logging to use an app's logs/ directory.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
app_dir: The app's root directory (containing logs/)
|
|
77
|
+
"""
|
|
78
|
+
logs_dir = app_dir / "logs"
|
|
79
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
self.set_log_dir(logs_dir)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def get(cls) -> "ScryRunLogger":
|
|
84
|
+
"""Get singleton logger instance."""
|
|
85
|
+
if cls._instance is None:
|
|
86
|
+
cls._instance = cls()
|
|
87
|
+
return cls._instance
|
|
88
|
+
|
|
89
|
+
def enable(self):
|
|
90
|
+
"""Enable logging."""
|
|
91
|
+
self._enabled = True
|
|
92
|
+
|
|
93
|
+
def disable(self):
|
|
94
|
+
"""Disable logging."""
|
|
95
|
+
self._enabled = False
|
|
96
|
+
|
|
97
|
+
def _write(self, level: str, message: str, details: Optional[str] = None):
|
|
98
|
+
"""Write a log entry to file.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
level: Log level (DEBUG, INFO, WARN, ERROR)
|
|
102
|
+
message: Main log message
|
|
103
|
+
details: Optional detailed content (e.g., full response text)
|
|
104
|
+
"""
|
|
105
|
+
if not self._enabled:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
timestamp = datetime.datetime.now().isoformat()
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
with open(self.log_file, "a", encoding="utf-8") as f:
|
|
112
|
+
f.write(f"\n{'='*80}\n")
|
|
113
|
+
f.write(f"[{timestamp}] [{level}] {message}\n")
|
|
114
|
+
if details:
|
|
115
|
+
f.write(f"{'-'*40}\n")
|
|
116
|
+
f.write(details)
|
|
117
|
+
if not details.endswith("\n"):
|
|
118
|
+
f.write("\n")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
# Don't let logging errors break the main flow
|
|
121
|
+
print(f"[scry-run] Warning: Could not write to log file: {e}", file=sys.stderr)
|
|
122
|
+
|
|
123
|
+
def debug(self, message: str, details: Optional[str] = None):
|
|
124
|
+
"""Log debug message."""
|
|
125
|
+
self._write("DEBUG", message, details)
|
|
126
|
+
|
|
127
|
+
def info(self, message: str, details: Optional[str] = None):
|
|
128
|
+
"""Log info message."""
|
|
129
|
+
self._write("INFO", message, details)
|
|
130
|
+
|
|
131
|
+
def warn(self, message: str, details: Optional[str] = None):
|
|
132
|
+
"""Log warning message."""
|
|
133
|
+
self._write("WARN", message, details)
|
|
134
|
+
|
|
135
|
+
def error(self, message: str, details: Optional[str] = None):
|
|
136
|
+
"""Log error message."""
|
|
137
|
+
self._write("ERROR", message, details)
|
|
138
|
+
|
|
139
|
+
def log_generation_start(self, class_name: str, attr_name: str, prompt: str):
|
|
140
|
+
"""Log the start of a generation request."""
|
|
141
|
+
self.info(
|
|
142
|
+
f"Generation started: {class_name}.{attr_name}",
|
|
143
|
+
f"PROMPT:\n{prompt}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def log_generation_response(self, response_text: str):
|
|
147
|
+
"""Log the raw LLM response."""
|
|
148
|
+
self.debug(
|
|
149
|
+
"LLM Response received",
|
|
150
|
+
f"RESPONSE:\n{response_text}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def log_validation_error(self, error: Exception, code: Optional[str] = None):
|
|
154
|
+
"""Log a validation error with full details."""
|
|
155
|
+
details = f"ERROR: {error}\n\nTRACEBACK:\n{traceback.format_exc()}"
|
|
156
|
+
if code:
|
|
157
|
+
details += f"\n\nGENERATED CODE:\n{code}"
|
|
158
|
+
self.error("Validation failed", details)
|
|
159
|
+
|
|
160
|
+
def log_generation_success(self, class_name: str, attr_name: str, code: str):
|
|
161
|
+
"""Log successful generation."""
|
|
162
|
+
self.info(
|
|
163
|
+
f"Generation succeeded: {class_name}.{attr_name}",
|
|
164
|
+
f"GENERATED CODE:\n{code}"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Convenience function
|
|
169
|
+
def get_logger() -> ScryRunLogger:
|
|
170
|
+
"""Get the global logger instance."""
|
|
171
|
+
return ScryRunLogger.get()
|