scry-run 0.1.0__py3-none-any.whl → 0.1.1__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.
@@ -1,7 +1,7 @@
1
1
  """Frozen backend - errors on any generation attempt.
2
2
 
3
- This backend is used for "baked" apps that should only use cached methods.
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 baked/frozen app. The app needs to be "unbaked"
18
- or the method needs to be added to the cache before baking.
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 original app to generate this method, then re-bake\n"
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 for baked apps where all methods should come from cache.
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
@@ -370,6 +370,5 @@ def init(
370
370
  console.print(f" [cyan]scry-run run {name}[/cyan] Run your app")
371
371
  console.print(f" [cyan]scry-run info {name}[/cyan] View app details and cache stats")
372
372
  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
373
  console.print()
375
374
  console.print("[dim]Methods are generated on first use using Claude CLI.[/dim]")
scry_run/console.py CHANGED
@@ -1,52 +1,59 @@
1
1
  """Console output utilities for consistent styling."""
2
2
 
3
+ from datetime import datetime
4
+
3
5
  from rich.console import Console
4
6
 
5
7
  # Stderr console for status messages
6
8
  err_console = Console(stderr=True, highlight=False)
7
9
 
8
10
 
11
+ def _timestamp() -> str:
12
+ """Return current time as HH:MM:SS."""
13
+ return datetime.now().strftime("%H:%M:%S")
14
+
15
+
9
16
  def status(msg: str) -> None:
10
17
  """Print a dim status message."""
11
- err_console.print(f"[dim]\\[scry-run][/dim] {msg}")
18
+ err_console.print(f"[dim]{_timestamp()} \\[scry-run][/dim] {msg}")
12
19
 
13
20
 
14
21
  def info(msg: str) -> None:
15
22
  """Print an info message (cyan)."""
16
- err_console.print(f"[cyan]\\[scry-run][/cyan] {msg}")
23
+ err_console.print(f"[dim]{_timestamp()}[/dim] [cyan]\\[scry-run][/cyan] {msg}")
17
24
 
18
25
 
19
26
  def success(msg: str) -> None:
20
27
  """Print a success message (green)."""
21
- err_console.print(f"[green]\\[scry-run][/green] {msg}")
28
+ err_console.print(f"[dim]{_timestamp()}[/dim] [green]\\[scry-run][/green] {msg}")
22
29
 
23
30
 
24
31
  def warning(msg: str) -> None:
25
32
  """Print a warning message (yellow)."""
26
- err_console.print(f"[yellow]\\[scry-run][/yellow] {msg}")
33
+ err_console.print(f"[dim]{_timestamp()}[/dim] [yellow]\\[scry-run][/yellow] {msg}")
27
34
 
28
35
 
29
36
  def error(msg: str) -> None:
30
37
  """Print an error message (red)."""
31
- err_console.print(f"[red]\\[scry-run][/red] {msg}")
38
+ err_console.print(f"[dim]{_timestamp()}[/dim] [red]\\[scry-run][/red] {msg}")
32
39
 
33
40
 
34
41
  def generating(class_name: str, attr_name: str) -> None:
35
42
  """Print a 'generating' message."""
36
- err_console.print(f"[cyan]\\[scry-run][/cyan] Generating {class_name}.{attr_name}...")
43
+ err_console.print(f"[dim]{_timestamp()}[/dim] [cyan]\\[scry-run][/cyan] Generating {class_name}.{attr_name}...")
37
44
 
38
45
 
39
46
  def generated(class_name: str, attr_name: str) -> None:
40
47
  """Print a 'generated' success message."""
41
- err_console.print(f"[green]\\[scry-run][/green] Generated {class_name}.{attr_name} ✓")
48
+ err_console.print(f"[dim]{_timestamp()}[/dim] [green]\\[scry-run][/green] Generated {class_name}.{attr_name} ✓")
42
49
 
43
50
 
44
51
  def using_cached(class_name: str, attr_name: str) -> None:
45
52
  """Print a 'using cached' message."""
46
- err_console.print(f"[dim]\\[scry-run][/dim] Using cached {class_name}.{attr_name}")
53
+ err_console.print(f"[dim]{_timestamp()} \\[scry-run][/dim] Using cached {class_name}.{attr_name}")
47
54
 
48
55
 
49
56
  def backend_selected(backend_name: str, model: str | None, reason: str) -> None:
50
57
  """Print backend selection message."""
51
58
  model_str = f" (model={model})" if model else ""
52
- err_console.print(f"[dim]\\[scry-run][/dim] Using backend: {backend_name}{model_str} ({reason})")
59
+ 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/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
- # Update context with error feedback
1039
+ # Track this error
1039
1040
  if is_signature_error:
1040
- error_feedback = f"""
1041
- ## CRITICAL ERROR - SIGNATURE MISMATCH
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
- You previously generated code that caused this error when called:
1044
- TypeError: {e}
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
- The code was called with:
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
- You MUST fix the signature to match exactly how it is called.
1050
- """
1051
- else:
1052
- error_feedback = f"""
1053
- ## CRITICAL ERROR - RUNTIME EXCEPTION
1052
+ {errors_list}
1054
1053
 
1055
- You previously generated code that crashed with this error when executed:
1056
- {type(e).__name__}: {e}
1054
+ The code was called with:
1055
+ {chr(10).join(args_desc) if args_desc else "No arguments ()"}
1057
1056
 
1058
- This error occurred INSIDE your generated code.
1059
- Please analyze the error and FIX the code to handle this case.
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" for baked apps)
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.1
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
+ ![Hello Demo](demos/hello.gif)
64
+
65
+ ### Building a maze game with PyGame
66
+
67
+ ![Maze Demo](demos/maze.gif)
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=r8IO8zw5A132Yqik3Y2K2IzbFLNW3hFcyhGo1bup2Yk,1695
4
+ scry_run/console.py,sha256=1lVWZ-7tfbapl1wSYZ7zOWBwgKSn6LgR0QZYneILsgQ,2044
5
5
  scry_run/context.py,sha256=os3kdrYxcgzrkPPiPzIy9Lrlp4b964eishDb7KSCaCY,10256
6
- scry_run/generator.py,sha256=KrlfdwwDyp5DRRTD9vPRUkweWGAMU85z2EnjznKLXj4,25023
6
+ scry_run/generator.py,sha256=GjDGBgbes58L4Amo-tuXodfs7Tx2vGn_CO1YVdJMVjo,26422
7
7
  scry_run/home.py,sha256=-ZOgvsaJ0udH_a0_A0YYBZHjtLzGMVaU_t5NqmcBNIQ,1364
8
8
  scry_run/logging.py,sha256=zKB_-IgRT9GXVULaFJKepX4oUVXQlt3buqjzBoh9Uyo,5684
9
- scry_run/meta.py,sha256=z71xDV91pEi2-SG3MD-_SFwkWEHdgDMvbaQyxYHgCR4,71310
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=DfPWpD0Yq5wV5-kQtEGAuRWfv1q4Z66MPpmPIs-ZbEw,2863
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=DsWyzE1qN6gYMwHhGljIhp-mpMn4xODFQ2bSLJt1ELM,13603
21
+ scry_run/cli/init.py,sha256=awBdgL0gILM5RJBKp2ZfuvkqmMdew_drAPRqq5QSrRw,13509
22
22
  scry_run/cli/run.py,sha256=vH89DttVCQwNWWwvUNxo9IbjnCGU2F_1OeWrMxHsvc8,2327
23
- scry_run-0.1.0.dist-info/METADATA,sha256=lVPhCXWZSZPuSZWt3hT83vSSgfqA3DPkxvri8WXRFF8,6969
24
- scry_run-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
- scry_run-0.1.0.dist-info/entry_points.txt,sha256=edV5nv1PT_pjWsOcLR3DMNkV-MUBsCY-jORR4dee-Ho,47
26
- scry_run-0.1.0.dist-info/RECORD,,
23
+ scry_run-0.1.1.dist-info/METADATA,sha256=gZKL-lU4-KH79pCG0hFXnW7WsMtt7ovQ92s9INCvm_A,2651
24
+ scry_run-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
+ scry_run-0.1.1.dist-info/entry_points.txt,sha256=edV5nv1PT_pjWsOcLR3DMNkV-MUBsCY-jORR4dee-Ho,47
26
+ scry_run-0.1.1.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
- ![Hello Demo](demos/hello.gif)
112
-
113
- ### Building a maze game with PyGame
114
-
115
- ![Maze Demo](demos/maze.gif)
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