scry-run 0.1.0__py3-none-any.whl → 0.1.2__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/backends/frozen.py +6 -6
- scry_run/cli/init.py +73 -12
- scry_run/cli/run.py +25 -1
- scry_run/console.py +60 -16
- scry_run/generator.py +32 -1
- scry_run/logging.py +12 -9
- scry_run/meta.py +18 -17
- scry_run-0.1.2.dist-info/METADATA +105 -0
- {scry_run-0.1.0.dist-info → scry_run-0.1.2.dist-info}/RECORD +11 -11
- scry_run-0.1.0.dist-info/METADATA +0 -282
- {scry_run-0.1.0.dist-info → scry_run-0.1.2.dist-info}/WHEEL +0 -0
- {scry_run-0.1.0.dist-info → scry_run-0.1.2.dist-info}/entry_points.txt +0 -0
scry_run/backends/frozen.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Frozen backend - errors on any generation attempt.
|
|
2
2
|
|
|
3
|
-
This backend is used
|
|
4
|
-
Any attempt to generate new code will raise FrozenAppError.
|
|
3
|
+
This backend is used when code generation should be disabled and only cached methods
|
|
4
|
+
should be used. Any attempt to generate new code will raise FrozenAppError.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from typing import Optional
|
|
@@ -14,8 +14,8 @@ class FrozenAppError(RuntimeError):
|
|
|
14
14
|
"""Raised when a frozen app attempts to generate code.
|
|
15
15
|
|
|
16
16
|
This error indicates that a method was called that doesn't exist
|
|
17
|
-
in the cache of a
|
|
18
|
-
or the method needs to be
|
|
17
|
+
in the cache of a frozen app. The backend needs to be changed to allow
|
|
18
|
+
generation, or the method needs to be pre-generated and cached.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
def __init__(self, class_name: str, attr_name: str):
|
|
@@ -24,7 +24,7 @@ class FrozenAppError(RuntimeError):
|
|
|
24
24
|
super().__init__(
|
|
25
25
|
f"Frozen app cannot generate code for '{class_name}.{attr_name}'. "
|
|
26
26
|
f"This method is not in the cache. Either:\n"
|
|
27
|
-
f" 1. Run the
|
|
27
|
+
f" 1. Run the app with a real backend to generate this method first\n"
|
|
28
28
|
f" 2. Set _llm_backend to a real backend (e.g., 'claude') to unfreeze"
|
|
29
29
|
)
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ class FrozenAppError(RuntimeError):
|
|
|
32
32
|
class FrozenBackend(GeneratorBackend):
|
|
33
33
|
"""Backend that refuses to generate any code.
|
|
34
34
|
|
|
35
|
-
Used
|
|
35
|
+
Used when all methods should come from cache and generation is disabled.
|
|
36
36
|
Any generation attempt raises FrozenAppError with helpful message.
|
|
37
37
|
"""
|
|
38
38
|
|
scry_run/cli/init.py
CHANGED
|
@@ -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,11 +423,14 @@ 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")
|
|
372
434
|
console.print(f" [cyan]scry-run cache list {name}[/cyan] List generated methods")
|
|
373
|
-
console.print(f" [cyan]scry-run bake {name}[/cyan] Export as standalone package")
|
|
374
435
|
console.print()
|
|
375
436
|
console.print("[dim]Methods are generated on first use using Claude CLI.[/dim]")
|
scry_run/cli/run.py
CHANGED
|
@@ -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)
|
scry_run/console.py
CHANGED
|
@@ -1,52 +1,96 @@
|
|
|
1
1
|
"""Console output utilities for consistent styling."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
3
7
|
from rich.console import Console
|
|
4
8
|
|
|
5
9
|
# Stderr console for status messages
|
|
6
10
|
err_console = Console(stderr=True, highlight=False)
|
|
7
11
|
|
|
8
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
|
+
|
|
27
|
+
def _timestamp() -> str:
|
|
28
|
+
"""Return current time as HH:MM:SS."""
|
|
29
|
+
return datetime.now().strftime("%H:%M:%S")
|
|
30
|
+
|
|
31
|
+
|
|
9
32
|
def status(msg: str) -> None:
|
|
10
|
-
"""Print a dim status message."""
|
|
11
|
-
|
|
33
|
+
"""Print a dim status message. Uses title bar in quiet mode."""
|
|
34
|
+
if _is_quiet():
|
|
35
|
+
_set_title(msg)
|
|
36
|
+
return
|
|
37
|
+
err_console.print(f"[dim]{_timestamp()} \\[scry-run][/dim] {msg}")
|
|
12
38
|
|
|
13
39
|
|
|
14
40
|
def info(msg: str) -> None:
|
|
15
|
-
"""Print an info message (cyan)."""
|
|
16
|
-
|
|
41
|
+
"""Print an info message (cyan). Uses title bar in quiet mode."""
|
|
42
|
+
if _is_quiet():
|
|
43
|
+
_set_title(msg)
|
|
44
|
+
return
|
|
45
|
+
err_console.print(f"[dim]{_timestamp()}[/dim] [cyan]\\[scry-run][/cyan] {msg}")
|
|
17
46
|
|
|
18
47
|
|
|
19
48
|
def success(msg: str) -> None:
|
|
20
|
-
"""Print a success message (green)."""
|
|
21
|
-
|
|
49
|
+
"""Print a success message (green). Uses title bar in quiet mode."""
|
|
50
|
+
if _is_quiet():
|
|
51
|
+
_set_title(f"✓ {msg}")
|
|
52
|
+
return
|
|
53
|
+
err_console.print(f"[dim]{_timestamp()}[/dim] [green]\\[scry-run][/green] {msg}")
|
|
22
54
|
|
|
23
55
|
|
|
24
56
|
def warning(msg: str) -> None:
|
|
25
57
|
"""Print a warning message (yellow)."""
|
|
26
|
-
err_console.print(f"[yellow]\\[scry-run][/yellow] {msg}")
|
|
58
|
+
err_console.print(f"[dim]{_timestamp()}[/dim] [yellow]\\[scry-run][/yellow] {msg}")
|
|
27
59
|
|
|
28
60
|
|
|
29
61
|
def error(msg: str) -> None:
|
|
30
62
|
"""Print an error message (red)."""
|
|
31
|
-
err_console.print(f"[red]\\[scry-run][/red] {msg}")
|
|
63
|
+
err_console.print(f"[dim]{_timestamp()}[/dim] [red]\\[scry-run][/red] {msg}")
|
|
32
64
|
|
|
33
65
|
|
|
34
66
|
def generating(class_name: str, attr_name: str) -> None:
|
|
35
|
-
"""Print a 'generating' message."""
|
|
36
|
-
|
|
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
|
|
71
|
+
err_console.print(f"[dim]{_timestamp()}[/dim] [cyan]\\[scry-run][/cyan] Generating {class_name}.{attr_name}...")
|
|
37
72
|
|
|
38
73
|
|
|
39
74
|
def generated(class_name: str, attr_name: str) -> None:
|
|
40
|
-
"""Print a 'generated' success message."""
|
|
41
|
-
|
|
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
|
|
79
|
+
err_console.print(f"[dim]{_timestamp()}[/dim] [green]\\[scry-run][/green] Generated {class_name}.{attr_name} ✓")
|
|
42
80
|
|
|
43
81
|
|
|
44
82
|
def using_cached(class_name: str, attr_name: str) -> None:
|
|
45
|
-
"""Print a 'using cached' message."""
|
|
46
|
-
|
|
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
|
|
87
|
+
err_console.print(f"[dim]{_timestamp()} \\[scry-run][/dim] Using cached {class_name}.{attr_name}")
|
|
47
88
|
|
|
48
89
|
|
|
49
90
|
def backend_selected(backend_name: str, model: str | None, reason: str) -> None:
|
|
50
|
-
"""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
|
|
51
95
|
model_str = f" (model={model})" if model else ""
|
|
52
|
-
err_console.print(f"[dim]\\[scry-run][/dim] Using backend: {backend_name}{model_str} ({reason})")
|
|
96
|
+
err_console.print(f"[dim]{_timestamp()} \\[scry-run][/dim] Using backend: {backend_name}{model_str} ({reason})")
|
scry_run/generator.py
CHANGED
|
@@ -603,8 +603,31 @@ IMPORTANT: Output ONLY the JSON object, no markdown, no explanation, no code fen
|
|
|
603
603
|
|
|
604
604
|
# Delegate to backend with retries
|
|
605
605
|
last_error: Optional[Exception] = None
|
|
606
|
+
previous_errors: list[str] = [] # Track errors for feedback
|
|
606
607
|
|
|
607
608
|
for attempt in range(self.MAX_RETRIES):
|
|
609
|
+
# Build error feedback for retry attempts
|
|
610
|
+
error_feedback = ""
|
|
611
|
+
if previous_errors:
|
|
612
|
+
error_feedback = f"""
|
|
613
|
+
|
|
614
|
+
## PREVIOUS GENERATION FAILED
|
|
615
|
+
|
|
616
|
+
This is generation attempt #{attempt + 1}. Previous attempt(s) caused errors:
|
|
617
|
+
|
|
618
|
+
{chr(10).join(f"- Attempt #{i+1}: {err}" for i, err in enumerate(previous_errors))}
|
|
619
|
+
|
|
620
|
+
Please analyze these errors and generate FIXED code that avoids them.
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
# Rebuild prompt with error feedback if needed
|
|
624
|
+
if error_feedback:
|
|
625
|
+
if supports_context:
|
|
626
|
+
prompt = self._build_frame_prompt(class_name, attr_name, is_classmethod, runtime_context + error_feedback)
|
|
627
|
+
else:
|
|
628
|
+
prompt = self._build_prompt(context + error_feedback, class_name, attr_name, is_classmethod, installed_packages)
|
|
629
|
+
logger.log_generation_start(class_name, attr_name, prompt)
|
|
630
|
+
|
|
608
631
|
try:
|
|
609
632
|
result = self._backend.generate_code(prompt)
|
|
610
633
|
|
|
@@ -645,7 +668,15 @@ IMPORTANT: Output ONLY the JSON object, no markdown, no explanation, no code fen
|
|
|
645
668
|
# Log the validation error with full details
|
|
646
669
|
code_str = result.code if 'result' in dir() and hasattr(result, 'code') else None
|
|
647
670
|
logger.log_validation_error(e, code_str)
|
|
648
|
-
|
|
671
|
+
|
|
672
|
+
# Track error for feedback on next attempt
|
|
673
|
+
error_msg = f"{type(e).__name__}: {e}"
|
|
674
|
+
if code_str:
|
|
675
|
+
# Include a snippet of the problematic code
|
|
676
|
+
code_preview = code_str[:200] + "..." if len(code_str) > 200 else code_str
|
|
677
|
+
error_msg += f"\n Generated code (preview): {code_preview}"
|
|
678
|
+
previous_errors.append(error_msg)
|
|
679
|
+
|
|
649
680
|
if attempt >= self.MAX_RETRIES - 1:
|
|
650
681
|
raise
|
|
651
682
|
warning(f"Validation error, retrying ({attempt + 1}/{self.MAX_RETRIES})...")
|
scry_run/logging.py
CHANGED
|
@@ -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:
|
scry_run/meta.py
CHANGED
|
@@ -995,6 +995,7 @@ Requirements:
|
|
|
995
995
|
MAX_RETRIES = 3
|
|
996
996
|
last_error = None
|
|
997
997
|
current_context = call_context
|
|
998
|
+
previous_errors: list[str] = [] # Track all errors for accumulated feedback
|
|
998
999
|
|
|
999
1000
|
for attempt in range(MAX_RETRIES + 1):
|
|
1000
1001
|
# Generate code
|
|
@@ -1035,28 +1036,28 @@ Requirements:
|
|
|
1035
1036
|
# Invalidate cache so _llm_generate actually generates new code
|
|
1036
1037
|
self._cls._get_cache().prune(self._cls.__name__, self._name)
|
|
1037
1038
|
|
|
1038
|
-
#
|
|
1039
|
+
# Track this error
|
|
1039
1040
|
if is_signature_error:
|
|
1040
|
-
|
|
1041
|
-
|
|
1041
|
+
previous_errors.append(f"Attempt #{attempt + 1} - TypeError (signature mismatch): {e}")
|
|
1042
|
+
else:
|
|
1043
|
+
previous_errors.append(f"Attempt #{attempt + 1} - {type(e).__name__}: {e}")
|
|
1042
1044
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
+
# Build accumulated error feedback
|
|
1046
|
+
errors_list = chr(10).join(f" {err}" for err in previous_errors)
|
|
1047
|
+
error_feedback = f"""
|
|
1048
|
+
## CRITICAL - PREVIOUS GENERATION FAILED
|
|
1045
1049
|
|
|
1046
|
-
|
|
1047
|
-
{chr(10).join(args_desc) if args_desc else "No arguments ()"}
|
|
1050
|
+
This is generation attempt #{attempt + 2}. Previous attempt(s) caused errors:
|
|
1048
1051
|
|
|
1049
|
-
|
|
1050
|
-
"""
|
|
1051
|
-
else:
|
|
1052
|
-
error_feedback = f"""
|
|
1053
|
-
## CRITICAL ERROR - RUNTIME EXCEPTION
|
|
1052
|
+
{errors_list}
|
|
1054
1053
|
|
|
1055
|
-
|
|
1056
|
-
{
|
|
1054
|
+
The code was called with:
|
|
1055
|
+
{chr(10).join(args_desc) if args_desc else "No arguments ()"}
|
|
1057
1056
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1057
|
+
You MUST analyze ALL previous errors and generate FIXED code that:
|
|
1058
|
+
1. Has the correct signature matching how it is called
|
|
1059
|
+
2. Handles edge cases that caused runtime exceptions
|
|
1060
|
+
3. Does NOT repeat the same mistakes
|
|
1060
1061
|
"""
|
|
1061
1062
|
|
|
1062
1063
|
current_context = call_context + error_feedback
|
|
@@ -1356,7 +1357,7 @@ class ScryClass(metaclass=ScryMeta):
|
|
|
1356
1357
|
_llm_cache: Optional["ScryCache"] = None
|
|
1357
1358
|
_llm_generator: Optional["CodeGenerator"] = None
|
|
1358
1359
|
_llm_model: Optional[str] = None # Model override (e.g., "opus" for Claude)
|
|
1359
|
-
_llm_backend: Optional[str] = None # Backend override (e.g., "frozen"
|
|
1360
|
+
_llm_backend: Optional[str] = None # Backend override (e.g., "frozen" to prevent generation)
|
|
1360
1361
|
_llm_use_full_context: bool = True
|
|
1361
1362
|
_llm_quiet: bool = False # Set to True to suppress generation messages
|
|
1362
1363
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scry-run
|
|
3
|
+
Version: 0.1.2
|
|
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
|
+
Project-URL: Homepage, https://github.com/Tener/scry-run
|
|
6
|
+
Project-URL: Repository, https://github.com/Tener/scry-run
|
|
7
|
+
Author: Krzysztof Skrzętnicki
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: ai,claude,code-generation,llm,metaclass
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: claude-agent-sdk>=0.1.0
|
|
20
|
+
Requires-Dist: click>=8.0.0
|
|
21
|
+
Requires-Dist: jinja2>=3.0.0
|
|
22
|
+
Requires-Dist: rich>=13.0.0
|
|
23
|
+
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-timeout>=2.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# scry-run
|
|
32
|
+
|
|
33
|
+
**Describe an app, run it.** A CLI tool that generates and runs Python applications from natural language descriptions.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv tool install scry-run
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or run without installing:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uvx scry-run --help
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Requires [Claude Code CLI](https://github.com/anthropics/claude-code) to be installed.
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Create an app
|
|
53
|
+
scry-run init --name=todoist --description='minimal web todo app'
|
|
54
|
+
|
|
55
|
+
# Run it
|
|
56
|
+
scry-run run todoist
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Demos
|
|
60
|
+
|
|
61
|
+
### Creating a simple Hello World app
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
### Building a maze game with PyGame
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
## CLI Commands
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Create a new app
|
|
73
|
+
scry-run init --name=NAME --description=DESC
|
|
74
|
+
|
|
75
|
+
# List all apps
|
|
76
|
+
scry-run list
|
|
77
|
+
|
|
78
|
+
# Run an app
|
|
79
|
+
scry-run run myapp [args...]
|
|
80
|
+
|
|
81
|
+
# Get app info
|
|
82
|
+
scry-run info myapp
|
|
83
|
+
scry-run which myapp
|
|
84
|
+
|
|
85
|
+
# Remove or reset an app
|
|
86
|
+
scry-run rm myapp
|
|
87
|
+
scry-run reset myapp
|
|
88
|
+
|
|
89
|
+
# Cache management
|
|
90
|
+
scry-run cache list myapp
|
|
91
|
+
scry-run cache show myapp MyClass.method
|
|
92
|
+
scry-run cache export myapp --output=code.py
|
|
93
|
+
scry-run cache prune myapp --all
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
| Variable | Description | Default |
|
|
99
|
+
|----------|-------------|---------|
|
|
100
|
+
| `SCRY_RUN_BACKEND` | Backend: `claude`, `frozen`, or `auto` | `auto` |
|
|
101
|
+
| `SCRY_RUN_MODEL` | Model override (e.g., `opus`) | (backend default) |
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
scry_run/__init__.py,sha256=uAFNmvmMzEpsFNJ3Uew4RUSgNPXvn_cFkTU2Bx8RmWo,2875
|
|
2
2
|
scry_run/cache.py,sha256=gzWNCRva4d1KOqrYx0eo0T9o8lbsBYmcxgnXPUfc9Dg,14163
|
|
3
3
|
scry_run/config.py,sha256=zBnxbzHKwWDpg0C1BmSm6SGv5PF7rq_cSctWIsm_oz0,4083
|
|
4
|
-
scry_run/console.py,sha256=
|
|
4
|
+
scry_run/console.py,sha256=hCFjBQPFq2ok-EtuH3anVcsyEOERFgNDTMtb2SLMWZE,3341
|
|
5
5
|
scry_run/context.py,sha256=os3kdrYxcgzrkPPiPzIy9Lrlp4b964eishDb7KSCaCY,10256
|
|
6
|
-
scry_run/generator.py,sha256=
|
|
6
|
+
scry_run/generator.py,sha256=GjDGBgbes58L4Amo-tuXodfs7Tx2vGn_CO1YVdJMVjo,26422
|
|
7
7
|
scry_run/home.py,sha256=-ZOgvsaJ0udH_a0_A0YYBZHjtLzGMVaU_t5NqmcBNIQ,1364
|
|
8
|
-
scry_run/logging.py,sha256=
|
|
9
|
-
scry_run/meta.py,sha256=
|
|
8
|
+
scry_run/logging.py,sha256=58xegPlh6nD7iE-UR-HtVC-UPqAqhseCkORhtRjHBYY,5838
|
|
9
|
+
scry_run/meta.py,sha256=shKzrDc-lUWIS8MOjouoVzPuOt3rP5zpWv5BvjIGWC4,71598
|
|
10
10
|
scry_run/packages.py,sha256=U6HxWoEkFisKwLAeM_RtFvTt2qrCmxmZD38pJNVQt7g,5156
|
|
11
11
|
scry_run/backends/__init__.py,sha256=q52KwLy2KsJLxpUJKbLsCTH0ELLLQGOz_14OnhDYOig,245
|
|
12
12
|
scry_run/backends/base.py,sha256=bedYMpVL19dE_ei85wWRj8g5zsJsH607G8nAgsDpfwI,1693
|
|
13
13
|
scry_run/backends/claude.py,sha256=fqYK1F1PsqCM2-ssvmdduhwjrsVURcLeCTJLxTbAKPE,15038
|
|
14
|
-
scry_run/backends/frozen.py,sha256=
|
|
14
|
+
scry_run/backends/frozen.py,sha256=69vqlztysFXuX7Nv5nu-bDqhan8F-0mHgGCC_4kOiQA,2912
|
|
15
15
|
scry_run/backends/registry.py,sha256=pAOrZcaWHJhIn6ai-O6lBzb0QeICa3MV-kH5zcDcRrM,2180
|
|
16
16
|
scry_run/cli/__init__.py,sha256=Hje8gmqzBsbX5iW6lKVfv5ZOXamkUVx-fGnwRupgmuU,5385
|
|
17
17
|
scry_run/cli/apps.py,sha256=AP016GpSH4_mPpXq3k8r4hjBE5Kizxk9mDQNZpdAt0c,12609
|
|
18
18
|
scry_run/cli/cache.py,sha256=UkxSFZUqiyDxn2KrFojh8UT03d_lBxJmolsV7w7qTmU,10774
|
|
19
19
|
scry_run/cli/config_cmd.py,sha256=Ge8Xw8R3_LpEfCOrXgaeD-0KG4WrVslMpNdZhtReOp8,2436
|
|
20
20
|
scry_run/cli/env.py,sha256=Jxw5I6TY5sJ0Rsha86OZDPRHDS850FA4n30sg4yFKhQ,745
|
|
21
|
-
scry_run/cli/init.py,sha256=
|
|
22
|
-
scry_run/cli/run.py,sha256=
|
|
23
|
-
scry_run-0.1.
|
|
24
|
-
scry_run-0.1.
|
|
25
|
-
scry_run-0.1.
|
|
26
|
-
scry_run-0.1.
|
|
21
|
+
scry_run/cli/init.py,sha256=JPI88yxNtwrp14k5CWRIFhd4Rbo5tW3qWC72KHdIs40,15742
|
|
22
|
+
scry_run/cli/run.py,sha256=qgqR-MGB1Vo9H4xWo23rThNhfpcA8xXn-fzFNwhMn5o,3161
|
|
23
|
+
scry_run-0.1.2.dist-info/METADATA,sha256=UBQM9IsTbacXE11Fq-aQ_xO2XI_LpkzKompmmufO_00,2651
|
|
24
|
+
scry_run-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
25
|
+
scry_run-0.1.2.dist-info/entry_points.txt,sha256=edV5nv1PT_pjWsOcLR3DMNkV-MUBsCY-jORR4dee-Ho,47
|
|
26
|
+
scry_run-0.1.2.dist-info/RECORD,,
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: scry-run
|
|
3
|
-
Version: 0.1.0
|
|
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
|
-
Project-URL: Homepage, https://github.com/Tener/scry-run
|
|
6
|
-
Project-URL: Repository, https://github.com/Tener/scry-run
|
|
7
|
-
Author: Krzysztof Skrzętnicki
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: ai,claude,code-generation,llm,metaclass
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Classifier: Topic :: Software Development :: Code Generators
|
|
18
|
-
Requires-Python: >=3.10
|
|
19
|
-
Requires-Dist: claude-agent-sdk>=0.1.0
|
|
20
|
-
Requires-Dist: click>=8.0.0
|
|
21
|
-
Requires-Dist: jinja2>=3.0.0
|
|
22
|
-
Requires-Dist: rich>=13.0.0
|
|
23
|
-
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
|
|
24
|
-
Provides-Extra: dev
|
|
25
|
-
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
26
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
27
|
-
Requires-Dist: pytest-timeout>=2.0.0; extra == 'dev'
|
|
28
|
-
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
|
|
31
|
-
# scry-run
|
|
32
|
-
|
|
33
|
-
**Write class definitions, get working code.** A Python library where any method call automatically generates its implementation via LLM.
|
|
34
|
-
|
|
35
|
-
Define your application as a class with a docstring, then call any method you want—`scry-run` generates the code on-demand, caches it, and executes it. No boilerplate, no implementation required.
|
|
36
|
-
|
|
37
|
-
## Features
|
|
38
|
-
|
|
39
|
-
- **Zero-boilerplate coding**: Define intent via class docstrings, methods generate automatically
|
|
40
|
-
- **Intelligent caching**: Generated code persists across runs—pay once, use forever
|
|
41
|
-
- **Production-ready**: Bake apps into standalone packages with frozen, pre-generated code
|
|
42
|
-
- **CLI-first**: Manage apps, inspect cache, and export generated code via intuitive commands
|
|
43
|
-
|
|
44
|
-
## Installation
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
pip install scry-run
|
|
48
|
-
# or with uv
|
|
49
|
-
uv pip install scry-run
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Run without installing (uvx)
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# From PyPI
|
|
56
|
-
uvx scry-run --help
|
|
57
|
-
uvx scry-run init --name=myapp --description="My app"
|
|
58
|
-
|
|
59
|
-
# From GitHub (latest)
|
|
60
|
-
uvx --from git+https://github.com/scry-run/scry-run scry-run --help
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Quick Start
|
|
64
|
-
|
|
65
|
-
### 1. Set up your backend
|
|
66
|
-
|
|
67
|
-
Requires Claude Code CLI to be installed.
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
# Verify claude CLI is available
|
|
71
|
-
claude --version
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### 2. Initialize a project
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
# Non-interactive
|
|
78
|
-
scry-run init --name=todoist --description='minimal web todo app'
|
|
79
|
-
|
|
80
|
-
# Interactive
|
|
81
|
-
scry-run init
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 3. Run your app
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
scry-run run todoist
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Or use it directly in Python:
|
|
91
|
-
|
|
92
|
-
```python
|
|
93
|
-
from scry_run import ScryClass
|
|
94
|
-
|
|
95
|
-
class Todoist(ScryClass):
|
|
96
|
-
"""A minimal web todo app with task management."""
|
|
97
|
-
pass
|
|
98
|
-
|
|
99
|
-
app = Todoist()
|
|
100
|
-
|
|
101
|
-
# These methods will be generated automatically!
|
|
102
|
-
app.add_task("Buy groceries")
|
|
103
|
-
app.list_tasks()
|
|
104
|
-
app.complete_task(0)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Demos
|
|
108
|
-
|
|
109
|
-
### Creating a simple Hello World app
|
|
110
|
-
|
|
111
|
-

|
|
112
|
-
|
|
113
|
-
### Building a maze game with PyGame
|
|
114
|
-
|
|
115
|
-

|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
## How It Works
|
|
119
|
-
|
|
120
|
-
1. When you access an undefined attribute on an `ScryClass` subclass, the metaclass intercepts it
|
|
121
|
-
2. The library collects the full codebase context and builds a prompt
|
|
122
|
-
3. The configured backend generates code with structured JSON output (code, type, docstring, dependencies)
|
|
123
|
-
4. The code is validated with Python's `ast.parse()` to ensure it's syntactically correct
|
|
124
|
-
5. Valid code is cached and executed
|
|
125
|
-
6. On subsequent accesses, the cached code is used directly
|
|
126
|
-
|
|
127
|
-
## Backend
|
|
128
|
-
|
|
129
|
-
Uses Claude Code CLI with `--print` flag for non-interactive output.
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
# If claude CLI is installed, it's used automatically
|
|
133
|
-
scry-run run myapp
|
|
134
|
-
|
|
135
|
-
# Optionally specify a model
|
|
136
|
-
export SCRY_RUN_MODEL=opus
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Configuration
|
|
140
|
-
|
|
141
|
-
### Environment Variables
|
|
142
|
-
|
|
143
|
-
| Variable | Description | Default |
|
|
144
|
-
|----------|-------------|---------|
|
|
145
|
-
| `SCRY_RUN_BACKEND` | Backend to use: `claude`, `frozen`, or `auto` | `auto` |
|
|
146
|
-
| `SCRY_RUN_MODEL` | Model override (e.g., `opus`) | (backend default) |
|
|
147
|
-
| `SCRY_RUN_MAX_GENERATIONS` | Max code generations per process | `100` |
|
|
148
|
-
|
|
149
|
-
### Class-level options
|
|
150
|
-
|
|
151
|
-
```python
|
|
152
|
-
class MyApp(ScryClass):
|
|
153
|
-
"""My application."""
|
|
154
|
-
|
|
155
|
-
# Disable LLM generation (raises AttributeError for missing attrs)
|
|
156
|
-
_llm_enabled = False
|
|
157
|
-
|
|
158
|
-
# Use minimal context instead of full codebase (faster)
|
|
159
|
-
_llm_use_full_context = False
|
|
160
|
-
|
|
161
|
-
# Suppress generation messages
|
|
162
|
-
_llm_quiet = True
|
|
163
|
-
|
|
164
|
-
# Override model for this class
|
|
165
|
-
_llm_model = "opus"
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## CLI Commands
|
|
169
|
-
|
|
170
|
-
### App Management
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
# Initialize a new app
|
|
174
|
-
scry-run init --name=NAME --description=DESC
|
|
175
|
-
|
|
176
|
-
# List all apps
|
|
177
|
-
scry-run list
|
|
178
|
-
|
|
179
|
-
# Get path to an app's main file
|
|
180
|
-
scry-run which myapp
|
|
181
|
-
|
|
182
|
-
# Run an app
|
|
183
|
-
scry-run run myapp [args...]
|
|
184
|
-
|
|
185
|
-
# Remove an app
|
|
186
|
-
scry-run rm myapp
|
|
187
|
-
scry-run rm myapp --force # Skip confirmation
|
|
188
|
-
|
|
189
|
-
# Reset an app (clear code & cache, keep name/description)
|
|
190
|
-
scry-run reset myapp
|
|
191
|
-
scry-run reset myapp --force # Skip confirmation
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Cache Management
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
# List cached entries for an app
|
|
198
|
-
scry-run cache list myapp
|
|
199
|
-
|
|
200
|
-
# Show a specific entry
|
|
201
|
-
scry-run cache show myapp MyClass.my_method
|
|
202
|
-
|
|
203
|
-
# Export cache
|
|
204
|
-
scry-run cache export myapp --output=generated.py --format=python
|
|
205
|
-
scry-run cache export myapp --output=cache.json --format=json
|
|
206
|
-
|
|
207
|
-
# Remove a specific entry
|
|
208
|
-
scry-run cache rm myapp MyClass.my_method
|
|
209
|
-
|
|
210
|
-
# Prune cache entries
|
|
211
|
-
scry-run cache prune myapp --class=MyClass --attr=my_method
|
|
212
|
-
scry-run cache prune myapp --class=MyClass # All methods of a class
|
|
213
|
-
scry-run cache prune myapp --all # Everything
|
|
214
|
-
|
|
215
|
-
# Reset cache (clear all entries)
|
|
216
|
-
scry-run cache reset myapp --force
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## API
|
|
220
|
-
|
|
221
|
-
### ScryClass
|
|
222
|
-
|
|
223
|
-
Base class to inherit from for LLM-powered code generation.
|
|
224
|
-
|
|
225
|
-
```python
|
|
226
|
-
from scry_run import ScryClass
|
|
227
|
-
|
|
228
|
-
class MyApp(ScryClass):
|
|
229
|
-
"""Description of your app - the LLM uses this!"""
|
|
230
|
-
|
|
231
|
-
def existing_method(self):
|
|
232
|
-
"""Existing methods are used as context."""
|
|
233
|
-
return "hello"
|
|
234
|
-
|
|
235
|
-
# Class methods
|
|
236
|
-
MyApp.llm_export_cache("output.py") # Export generated code
|
|
237
|
-
MyApp.llm_prune_cache("method_name") # Remove cached entry
|
|
238
|
-
MyApp.llm_disable() # Disable generation
|
|
239
|
-
MyApp.llm_enable() # Re-enable generation
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### ScryCache
|
|
243
|
-
|
|
244
|
-
Manage the code cache programmatically.
|
|
245
|
-
|
|
246
|
-
```python
|
|
247
|
-
from scry_run import ScryCache
|
|
248
|
-
|
|
249
|
-
cache = ScryCache()
|
|
250
|
-
|
|
251
|
-
# Get cached code
|
|
252
|
-
entry = cache.get("MyClass", "my_method")
|
|
253
|
-
print(entry.code)
|
|
254
|
-
|
|
255
|
-
# List all entries
|
|
256
|
-
for entry in cache.list_entries():
|
|
257
|
-
print(f"{entry.class_name}.{entry.attr_name}: {entry.docstring}")
|
|
258
|
-
|
|
259
|
-
# Export
|
|
260
|
-
cache.export_to_file("all_generated_code.py")
|
|
261
|
-
|
|
262
|
-
# Prune
|
|
263
|
-
cache.prune(class_name="MyClass", attr_name="my_method")
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
## Development
|
|
267
|
-
|
|
268
|
-
```bash
|
|
269
|
-
# Clone the repo
|
|
270
|
-
git clone https://github.com/scry-run/scry-run
|
|
271
|
-
cd scry-run
|
|
272
|
-
|
|
273
|
-
# Install with dev dependencies
|
|
274
|
-
uv sync --dev
|
|
275
|
-
|
|
276
|
-
# Run tests
|
|
277
|
-
uv run pytest tests/ -v
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
## License
|
|
281
|
-
|
|
282
|
-
MIT
|
|
File without changes
|
|
File without changes
|