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/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()