scry-run 0.1.1__tar.gz → 0.1.2__tar.gz
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-0.1.1 → scry_run-0.1.2}/.claude/settings.local.json +2 -1
- {scry_run-0.1.1 → scry_run-0.1.2}/PKG-INFO +1 -1
- {scry_run-0.1.1 → scry_run-0.1.2}/pyproject.toml +1 -1
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/cli/init.py +73 -11
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/cli/run.py +25 -1
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/console.py +44 -7
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/logging.py +12 -9
- {scry_run-0.1.1 → scry_run-0.1.2}/uv.lock +1 -1
- {scry_run-0.1.1 → scry_run-0.1.2}/.gitignore +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/README.md +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/__init__.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/backends/__init__.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/backends/base.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/backends/claude.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/backends/frozen.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/backends/registry.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/cache.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/cli/__init__.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/cli/apps.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/cli/cache.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/cli/config_cmd.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/cli/env.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/config.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/context.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/generator.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/home.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/meta.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/src/scry_run/packages.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/conftest.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_cache.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_cli.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_cli_default.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_cli_env.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_cli_run.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_config.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_context.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_generator.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_home.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_integration.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_logging.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_meta.py +0 -0
- {scry_run-0.1.1 → scry_run-0.1.2}/tests/test_packages.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scry-run
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: LLM-powered dynamic code generation via metaclasses. Define classes with docstrings, call any method—code generates automatically, caches persistently, and executes instantly.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Tener/scry-run
|
|
6
6
|
Project-URL: Repository, https://github.com/Tener/scry-run
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "scry-run"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "LLM-powered dynamic code generation via metaclasses. Define classes with docstrings, call any method—code generates automatically, caches persistently, and executes instantly."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -39,7 +39,7 @@ IMPORTANT GUIDELINES:
|
|
|
39
39
|
- STICK TO WHAT THE USER ASKED FOR - don't add unrelated features
|
|
40
40
|
- Less is more - a focused app is better than a bloated one
|
|
41
41
|
- If the user said "todo list", make a todo list - not a project management suite
|
|
42
|
-
- Total length: 100-200 words (keep it concise!)
|
|
42
|
+
- Total description length: 100-200 words (keep it concise!)
|
|
43
43
|
|
|
44
44
|
**OPEN WORLD PRINCIPLE**: This app uses scry-run for dynamic code generation. Methods are generated on-demand at runtime, meaning the set of commands is effectively unlimited - but inputs must still be STRUCTURED like a normal CLI:
|
|
45
45
|
- Use standard CLI patterns: commands, subcommands, flags, and positional arguments
|
|
@@ -52,10 +52,23 @@ IMPORTANT GUIDELINES:
|
|
|
52
52
|
- GOOD: `todo add "buy milk"`, `todo list --due=today`, `todo done 3`
|
|
53
53
|
- BAD: `todo what's due today?` (question form)
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
**APP FLAGS** - Recommend runtime flags for the app:
|
|
56
|
+
- "quiet": true - For TUI/interactive apps (curses, textual, rich.live, pygame, etc.)
|
|
57
|
+
This shows scry-run status in terminal title bar instead of stderr, preventing display disruption.
|
|
58
|
+
Only set to true if the app uses a TUI or graphical library.
|
|
56
59
|
|
|
60
|
+
Return a JSON object with this structure:
|
|
61
|
+
{{
|
|
62
|
+
"description": "The expanded description text here...",
|
|
63
|
+
"flags": {{
|
|
64
|
+
"quiet": false
|
|
65
|
+
}}
|
|
66
|
+
}}
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
Return ONLY the JSON object, no markdown code fences or explanation.'''
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def expand_description(name: str, description: str) -> dict | None:
|
|
59
72
|
"""Use LLM to expand a brief description into a detailed one.
|
|
60
73
|
|
|
61
74
|
Args:
|
|
@@ -63,7 +76,7 @@ def expand_description(name: str, description: str) -> str | None:
|
|
|
63
76
|
description: User's brief description
|
|
64
77
|
|
|
65
78
|
Returns:
|
|
66
|
-
|
|
79
|
+
Dict with 'description' and 'flags' keys, or None if expansion fails
|
|
67
80
|
"""
|
|
68
81
|
try:
|
|
69
82
|
from scry_run.generator import CodeGenerator, ScryRunError
|
|
@@ -72,8 +85,32 @@ def expand_description(name: str, description: str) -> str | None:
|
|
|
72
85
|
prompt = EXPAND_PROMPT.format(name=name, description=description)
|
|
73
86
|
|
|
74
87
|
# Use generate_freeform for text generation
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
response = generator.generate_freeform(prompt)
|
|
89
|
+
response = response.strip()
|
|
90
|
+
|
|
91
|
+
# Parse JSON response
|
|
92
|
+
# Handle potential markdown code fences
|
|
93
|
+
if response.startswith("```"):
|
|
94
|
+
lines = response.split("\n")
|
|
95
|
+
# Remove first and last lines (code fences)
|
|
96
|
+
response = "\n".join(lines[1:-1])
|
|
97
|
+
|
|
98
|
+
result = json.loads(response)
|
|
99
|
+
|
|
100
|
+
# Validate structure
|
|
101
|
+
if "description" not in result:
|
|
102
|
+
console.print("[yellow]Warning:[/yellow] LLM response missing 'description' field")
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
# Ensure flags exists with defaults
|
|
106
|
+
if "flags" not in result:
|
|
107
|
+
result["flags"] = {}
|
|
108
|
+
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
except json.JSONDecodeError as e:
|
|
112
|
+
console.print(f"[yellow]Warning:[/yellow] Could not parse LLM response as JSON: {e}")
|
|
113
|
+
return None
|
|
77
114
|
except ScryRunError as e:
|
|
78
115
|
console.print(f"[yellow]Warning:[/yellow] Could not expand description: {e.message}")
|
|
79
116
|
return None
|
|
@@ -279,20 +316,31 @@ def init(
|
|
|
279
316
|
)
|
|
280
317
|
|
|
281
318
|
# Auto-expand description using LLM
|
|
319
|
+
# Track recommended flags from LLM
|
|
320
|
+
recommended_flags: dict = {}
|
|
321
|
+
|
|
282
322
|
if auto_expand:
|
|
283
323
|
console.print()
|
|
284
324
|
console.print("[dim]Expanding description with AI...[/dim]")
|
|
285
|
-
|
|
325
|
+
result = expand_description(name, description)
|
|
326
|
+
|
|
327
|
+
if result:
|
|
328
|
+
expanded_desc = result["description"]
|
|
329
|
+
recommended_flags = result.get("flags", {})
|
|
286
330
|
|
|
287
|
-
if expanded:
|
|
288
331
|
# Show the expanded description
|
|
289
332
|
console.print()
|
|
290
333
|
panel = Panel(
|
|
291
|
-
|
|
334
|
+
expanded_desc,
|
|
292
335
|
title="[bold]Expanded Description[/bold]",
|
|
293
336
|
border_style="blue",
|
|
294
337
|
)
|
|
295
338
|
console.print(panel)
|
|
339
|
+
|
|
340
|
+
# Show recommended flags if any are non-default
|
|
341
|
+
if recommended_flags.get("quiet"):
|
|
342
|
+
console.print("[dim]Recommended: quiet mode (TUI app)[/dim]")
|
|
343
|
+
|
|
296
344
|
console.print()
|
|
297
345
|
|
|
298
346
|
# Let user confirm, edit, or reject
|
|
@@ -303,10 +351,10 @@ def init(
|
|
|
303
351
|
default="y",
|
|
304
352
|
)
|
|
305
353
|
if choice == "y":
|
|
306
|
-
description =
|
|
354
|
+
description = expanded_desc
|
|
307
355
|
elif choice == "e":
|
|
308
356
|
# Open in editor
|
|
309
|
-
edited = click.edit(
|
|
357
|
+
edited = click.edit(expanded_desc)
|
|
310
358
|
if edited:
|
|
311
359
|
description = edited.strip()
|
|
312
360
|
console.print("[dim]Using edited description.[/dim]")
|
|
@@ -314,6 +362,7 @@ def init(
|
|
|
314
362
|
console.print("[dim]Editor returned empty, using original.[/dim]")
|
|
315
363
|
else:
|
|
316
364
|
console.print("[dim]Using original description.[/dim]")
|
|
365
|
+
recommended_flags = {} # Reset flags if user rejected expansion
|
|
317
366
|
else:
|
|
318
367
|
console.print("[dim]Using original description.[/dim]")
|
|
319
368
|
|
|
@@ -356,6 +405,15 @@ def init(
|
|
|
356
405
|
cache_file = app_dir / "cache.json"
|
|
357
406
|
cache_file.write_text(json.dumps({}))
|
|
358
407
|
|
|
408
|
+
# Create settings.json with default flags from LLM recommendation
|
|
409
|
+
settings = {
|
|
410
|
+
"default_flags": {
|
|
411
|
+
"quiet": recommended_flags.get("quiet", False),
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
settings_file = app_dir / "settings.json"
|
|
415
|
+
settings_file.write_text(json.dumps(settings, indent=2))
|
|
416
|
+
|
|
359
417
|
# Create logs directory
|
|
360
418
|
logs_dir = app_dir / "logs"
|
|
361
419
|
logs_dir.mkdir(exist_ok=True)
|
|
@@ -365,7 +423,11 @@ def init(
|
|
|
365
423
|
console.print()
|
|
366
424
|
console.print(f" [dim]Location:[/dim] {app_dir}")
|
|
367
425
|
console.print(f" [dim]Main file:[/dim] {app_file}")
|
|
426
|
+
console.print(f" [dim]Settings:[/dim] {settings_file}")
|
|
368
427
|
console.print()
|
|
428
|
+
if recommended_flags.get("quiet"):
|
|
429
|
+
console.print("[dim]Quiet mode enabled (TUI app) - status shown in title bar.[/dim]")
|
|
430
|
+
console.print()
|
|
369
431
|
console.print("[bold]Useful commands:[/bold]")
|
|
370
432
|
console.print(f" [cyan]scry-run run {name}[/cyan] Run your app")
|
|
371
433
|
console.print(f" [cyan]scry-run info {name}[/cyan] View app details and cache stats")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Run command for executing scry-run apps."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import os
|
|
4
5
|
import subprocess
|
|
5
6
|
|
|
@@ -13,11 +14,23 @@ from scry_run.packages import ensure_scry_run_installed
|
|
|
13
14
|
console = Console()
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
def load_app_settings(app_dir) -> dict:
|
|
18
|
+
"""Load per-app settings from settings.json."""
|
|
19
|
+
settings_file = app_dir / "settings.json"
|
|
20
|
+
if settings_file.exists():
|
|
21
|
+
try:
|
|
22
|
+
return json.loads(settings_file.read_text())
|
|
23
|
+
except (json.JSONDecodeError, OSError):
|
|
24
|
+
return {}
|
|
25
|
+
return {}
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
@click.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
|
|
29
|
+
@click.option("--quiet", "-q", is_flag=True, help="Suppress status messages (for TUI apps)")
|
|
17
30
|
@click.argument("app_name")
|
|
18
31
|
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
19
32
|
@click.pass_context
|
|
20
|
-
def run(ctx, app_name: str, args: tuple[str, ...]) -> None:
|
|
33
|
+
def run(ctx, quiet: bool, app_name: str, args: tuple[str, ...]) -> None:
|
|
21
34
|
"""Run an scry-run app.
|
|
22
35
|
|
|
23
36
|
Loads config from ~/.scry-run/config.toml, converts to env vars,
|
|
@@ -28,6 +41,7 @@ def run(ctx, app_name: str, args: tuple[str, ...]) -> None:
|
|
|
28
41
|
scry-run run todo-app
|
|
29
42
|
scry-run run todo-app add "Buy milk"
|
|
30
43
|
scry-run run todo-app --help
|
|
44
|
+
scry-run run --quiet my-tui-app
|
|
31
45
|
"""
|
|
32
46
|
# Find app
|
|
33
47
|
app_dir = get_app_dir(app_name)
|
|
@@ -41,6 +55,10 @@ def run(ctx, app_name: str, args: tuple[str, ...]) -> None:
|
|
|
41
55
|
console.print(f" [cyan]scry-run init --name {app_name} --description '...'[/cyan]")
|
|
42
56
|
ctx.exit(1)
|
|
43
57
|
|
|
58
|
+
# Load per-app settings
|
|
59
|
+
app_settings = load_app_settings(app_dir)
|
|
60
|
+
default_flags = app_settings.get("default_flags", {})
|
|
61
|
+
|
|
44
62
|
# Load config and convert to env vars
|
|
45
63
|
config = load_config()
|
|
46
64
|
env_vars = get_env_vars(config)
|
|
@@ -49,6 +67,12 @@ def run(ctx, app_name: str, args: tuple[str, ...]) -> None:
|
|
|
49
67
|
env = os.environ.copy()
|
|
50
68
|
env.update(env_vars)
|
|
51
69
|
|
|
70
|
+
# Set quiet mode if requested via flag OR app default
|
|
71
|
+
# CLI flag takes precedence over app settings
|
|
72
|
+
use_quiet = quiet or default_flags.get("quiet", False)
|
|
73
|
+
if use_quiet:
|
|
74
|
+
env["SCRY_QUIET"] = "1"
|
|
75
|
+
|
|
52
76
|
# Clear venv-related environment variables to prevent interference
|
|
53
77
|
# from an activated venv (uv run --directory will use the app's venv)
|
|
54
78
|
env.pop("VIRTUAL_ENV", None)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Console output utilities for consistent styling."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
3
5
|
from datetime import datetime
|
|
4
6
|
|
|
5
7
|
from rich.console import Console
|
|
@@ -8,23 +10,46 @@ from rich.console import Console
|
|
|
8
10
|
err_console = Console(stderr=True, highlight=False)
|
|
9
11
|
|
|
10
12
|
|
|
13
|
+
def _is_quiet() -> bool:
|
|
14
|
+
"""Check if quiet mode is enabled (SCRY_QUIET=1)."""
|
|
15
|
+
return os.environ.get("SCRY_QUIET", "").lower() in ("1", "true", "yes", "on")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _set_title(msg: str) -> None:
|
|
19
|
+
"""Set terminal title bar. Used in quiet mode to show progress."""
|
|
20
|
+
# OSC escape sequence to set window title
|
|
21
|
+
# \033]0; sets both window title and icon name
|
|
22
|
+
# \007 is the bell character that terminates the sequence
|
|
23
|
+
sys.stderr.write(f"\033]0;[scry-run] {msg}\007")
|
|
24
|
+
sys.stderr.flush()
|
|
25
|
+
|
|
26
|
+
|
|
11
27
|
def _timestamp() -> str:
|
|
12
28
|
"""Return current time as HH:MM:SS."""
|
|
13
29
|
return datetime.now().strftime("%H:%M:%S")
|
|
14
30
|
|
|
15
31
|
|
|
16
32
|
def status(msg: str) -> None:
|
|
17
|
-
"""Print a dim status message."""
|
|
33
|
+
"""Print a dim status message. Uses title bar in quiet mode."""
|
|
34
|
+
if _is_quiet():
|
|
35
|
+
_set_title(msg)
|
|
36
|
+
return
|
|
18
37
|
err_console.print(f"[dim]{_timestamp()} \\[scry-run][/dim] {msg}")
|
|
19
38
|
|
|
20
39
|
|
|
21
40
|
def info(msg: str) -> None:
|
|
22
|
-
"""Print an info message (cyan)."""
|
|
41
|
+
"""Print an info message (cyan). Uses title bar in quiet mode."""
|
|
42
|
+
if _is_quiet():
|
|
43
|
+
_set_title(msg)
|
|
44
|
+
return
|
|
23
45
|
err_console.print(f"[dim]{_timestamp()}[/dim] [cyan]\\[scry-run][/cyan] {msg}")
|
|
24
46
|
|
|
25
47
|
|
|
26
48
|
def success(msg: str) -> None:
|
|
27
|
-
"""Print a success message (green)."""
|
|
49
|
+
"""Print a success message (green). Uses title bar in quiet mode."""
|
|
50
|
+
if _is_quiet():
|
|
51
|
+
_set_title(f"✓ {msg}")
|
|
52
|
+
return
|
|
28
53
|
err_console.print(f"[dim]{_timestamp()}[/dim] [green]\\[scry-run][/green] {msg}")
|
|
29
54
|
|
|
30
55
|
|
|
@@ -39,21 +64,33 @@ def error(msg: str) -> None:
|
|
|
39
64
|
|
|
40
65
|
|
|
41
66
|
def generating(class_name: str, attr_name: str) -> None:
|
|
42
|
-
"""Print a 'generating' message."""
|
|
67
|
+
"""Print a 'generating' message. Uses title bar in quiet mode."""
|
|
68
|
+
if _is_quiet():
|
|
69
|
+
_set_title(f"Generating {class_name}.{attr_name}...")
|
|
70
|
+
return
|
|
43
71
|
err_console.print(f"[dim]{_timestamp()}[/dim] [cyan]\\[scry-run][/cyan] Generating {class_name}.{attr_name}...")
|
|
44
72
|
|
|
45
73
|
|
|
46
74
|
def generated(class_name: str, attr_name: str) -> None:
|
|
47
|
-
"""Print a 'generated' success message."""
|
|
75
|
+
"""Print a 'generated' success message. Uses title bar in quiet mode."""
|
|
76
|
+
if _is_quiet():
|
|
77
|
+
_set_title(f"✓ Generated {class_name}.{attr_name}")
|
|
78
|
+
return
|
|
48
79
|
err_console.print(f"[dim]{_timestamp()}[/dim] [green]\\[scry-run][/green] Generated {class_name}.{attr_name} ✓")
|
|
49
80
|
|
|
50
81
|
|
|
51
82
|
def using_cached(class_name: str, attr_name: str) -> None:
|
|
52
|
-
"""Print a 'using cached' message."""
|
|
83
|
+
"""Print a 'using cached' message. Uses title bar in quiet mode."""
|
|
84
|
+
if _is_quiet():
|
|
85
|
+
_set_title(f"Using cached {class_name}.{attr_name}")
|
|
86
|
+
return
|
|
53
87
|
err_console.print(f"[dim]{_timestamp()} \\[scry-run][/dim] Using cached {class_name}.{attr_name}")
|
|
54
88
|
|
|
55
89
|
|
|
56
90
|
def backend_selected(backend_name: str, model: str | None, reason: str) -> None:
|
|
57
|
-
"""Print backend selection message."""
|
|
91
|
+
"""Print backend selection message. Uses title bar in quiet mode."""
|
|
92
|
+
if _is_quiet():
|
|
93
|
+
_set_title(f"Backend: {backend_name}")
|
|
94
|
+
return
|
|
58
95
|
model_str = f" (model={model})" if model else ""
|
|
59
96
|
err_console.print(f"[dim]{_timestamp()} \\[scry-run][/dim] Using backend: {backend_name}{model_str} ({reason})")
|
|
@@ -17,14 +17,14 @@ class ScryRunLogger:
|
|
|
17
17
|
Log file is created in:
|
|
18
18
|
1. The app's logs/ directory (if running in an app context)
|
|
19
19
|
2. Directory specified by SCRY_LOG_DIR
|
|
20
|
-
3.
|
|
20
|
+
3. ~/.scry-run/logs/ (fallback for CLI commands)
|
|
21
21
|
"""
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
_instance: Optional["ScryRunLogger"] = None
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
def __init__(self, log_dir: Optional[Path] = None):
|
|
26
26
|
"""Initialize logger.
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
Args:
|
|
29
29
|
log_dir: Directory for log file. If None, uses auto-detection.
|
|
30
30
|
"""
|
|
@@ -32,24 +32,27 @@ class ScryRunLogger:
|
|
|
32
32
|
self._log_file: Optional[Path] = None
|
|
33
33
|
# Session timestamp for unique log files per run
|
|
34
34
|
self._session_id = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
if log_dir:
|
|
37
37
|
self._log_dir = log_dir
|
|
38
38
|
else:
|
|
39
39
|
env_dir = os.environ.get("SCRY_LOG_DIR")
|
|
40
40
|
if env_dir:
|
|
41
41
|
self._log_dir = Path(env_dir)
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
# Debug logging is ON by default. Set SCRY_DEBUG=0 to disable.
|
|
44
44
|
debug_env = os.environ.get("SCRY_DEBUG", "").lower()
|
|
45
45
|
self._enabled = debug_env not in ("0", "false", "no", "off")
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
@property
|
|
48
48
|
def log_dir(self) -> Path:
|
|
49
|
-
"""Get log directory, with fallback to
|
|
49
|
+
"""Get log directory, with fallback to ~/.scry-run/logs/."""
|
|
50
50
|
if self._log_dir:
|
|
51
51
|
return self._log_dir
|
|
52
|
-
|
|
52
|
+
# Fallback to global scry-run logs directory (not cwd)
|
|
53
|
+
fallback = Path.home() / ".scry-run" / "logs"
|
|
54
|
+
fallback.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
return fallback
|
|
53
56
|
|
|
54
57
|
@property
|
|
55
58
|
def log_file(self) -> Path:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|