scry-run 0.1.1__tar.gz → 0.2.0__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.
Files changed (46) hide show
  1. scry_run-0.2.0/.claude/settings.local.json +20 -0
  2. {scry_run-0.1.1 → scry_run-0.2.0}/PKG-INFO +10 -5
  3. {scry_run-0.1.1 → scry_run-0.2.0}/README.md +8 -4
  4. {scry_run-0.1.1 → scry_run-0.2.0}/pyproject.toml +2 -1
  5. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/__init__.py +1 -1
  6. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/__init__.py +2 -0
  7. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/init.py +101 -28
  8. scry_run-0.2.0/src/scry_run/cli/log.py +373 -0
  9. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/run.py +39 -3
  10. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/config.py +4 -6
  11. scry_run-0.2.0/src/scry_run/console.py +104 -0
  12. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/logging.py +12 -9
  13. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/packages.py +20 -3
  14. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cli.py +1 -1
  15. scry_run-0.2.0/tests/test_cli_log.py +175 -0
  16. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cli_run.py +64 -4
  17. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_config.py +24 -7
  18. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_generator.py +14 -0
  19. {scry_run-0.1.1 → scry_run-0.2.0}/uv.lock +106 -1
  20. scry_run-0.1.1/.claude/settings.local.json +0 -12
  21. scry_run-0.1.1/src/scry_run/console.py +0 -59
  22. {scry_run-0.1.1 → scry_run-0.2.0}/.gitignore +0 -0
  23. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/__init__.py +0 -0
  24. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/base.py +0 -0
  25. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/claude.py +0 -0
  26. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/frozen.py +0 -0
  27. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/registry.py +0 -0
  28. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cache.py +0 -0
  29. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/apps.py +0 -0
  30. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/cache.py +0 -0
  31. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/config_cmd.py +0 -0
  32. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/env.py +0 -0
  33. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/context.py +0 -0
  34. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/generator.py +0 -0
  35. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/home.py +0 -0
  36. {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/meta.py +0 -0
  37. {scry_run-0.1.1 → scry_run-0.2.0}/tests/conftest.py +0 -0
  38. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cache.py +0 -0
  39. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cli_default.py +0 -0
  40. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cli_env.py +0 -0
  41. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_context.py +0 -0
  42. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_home.py +0 -0
  43. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_integration.py +0 -0
  44. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_logging.py +0 -0
  45. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_meta.py +0 -0
  46. {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_packages.py +0 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(find:*)",
5
+ "Bash(wc:*)",
6
+ "Bash(uv run pytest:*)",
7
+ "Bash(uv run python -m pytest:*)",
8
+ "Bash(uv sync:*)",
9
+ "Bash(uv lock:*)",
10
+ "Bash(uv run:*)",
11
+ "Bash(SCRY_BACKEND=claude uv run:*)",
12
+ "Bash(SCRY_RUN_HOME=/tmp/test_scry_home uv run:*)",
13
+ "Bash(SCRY_HOME=/tmp/test_scry_home uv run scry-run:*)",
14
+ "Bash(SCRY_HOME=/tmp/test_scry_home SCRY_MODEL=haiku uv run:*)",
15
+ "Bash(vhs:*)",
16
+ "WebFetch(domain:github.com)",
17
+ "WebFetch(domain:raw.githubusercontent.com)"
18
+ ]
19
+ }
20
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scry-run
3
- Version: 0.1.1
3
+ Version: 0.2.0
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
@@ -21,6 +21,7 @@ Requires-Dist: click>=8.0.0
21
21
  Requires-Dist: jinja2>=3.0.0
22
22
  Requires-Dist: rich>=13.0.0
23
23
  Requires-Dist: tomli>=2.0.0; python_version < '3.11'
24
+ Requires-Dist: watchfiles>=0.21.0
24
25
  Provides-Extra: dev
25
26
  Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
26
27
  Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
@@ -58,13 +59,17 @@ scry-run run todoist
58
59
 
59
60
  ## Demos
60
61
 
61
- ### Creating a simple Hello World app
62
+ ### Greet app
62
63
 
63
- ![Hello Demo](demos/hello.gif)
64
+ [![Greet Demo](demos/out/greet.gif)](demos/out/greet.webm)
64
65
 
65
- ### Building a maze game with PyGame
66
+ ### Timer app
66
67
 
67
- ![Maze Demo](demos/maze.gif)
68
+ [![Timer Demo](demos/out/timer.gif)](demos/out/timer.webm)
69
+
70
+ ### Maze app
71
+
72
+ [![Maze Demo](demos/out/maze.gif)](demos/out/maze.webm)
68
73
 
69
74
  ## CLI Commands
70
75
 
@@ -28,13 +28,17 @@ scry-run run todoist
28
28
 
29
29
  ## Demos
30
30
 
31
- ### Creating a simple Hello World app
31
+ ### Greet app
32
32
 
33
- ![Hello Demo](demos/hello.gif)
33
+ [![Greet Demo](demos/out/greet.gif)](demos/out/greet.webm)
34
34
 
35
- ### Building a maze game with PyGame
35
+ ### Timer app
36
36
 
37
- ![Maze Demo](demos/maze.gif)
37
+ [![Timer Demo](demos/out/timer.gif)](demos/out/timer.webm)
38
+
39
+ ### Maze app
40
+
41
+ [![Maze Demo](demos/out/maze.gif)](demos/out/maze.webm)
38
42
 
39
43
  ## CLI Commands
40
44
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scry-run"
3
- version = "0.1.1"
3
+ version = "0.2.0"
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" }
@@ -24,6 +24,7 @@ dependencies = [
24
24
  "jinja2>=3.0.0",
25
25
  "tomli>=2.0.0;python_version<'3.11'",
26
26
  "claude-agent-sdk>=0.1.0",
27
+ "watchfiles>=0.21.0",
27
28
  ]
28
29
 
29
30
  [project.optional-dependencies]
@@ -72,7 +72,7 @@ def is_generated(obj: Any) -> bool:
72
72
  return getattr(obj, marker, False)
73
73
 
74
74
 
75
- __version__ = "0.1.0"
75
+ __version__ = "0.2.0"
76
76
  __all__ = [
77
77
  "ScryClass",
78
78
  "ScryMeta",
@@ -13,6 +13,7 @@ from scry_run.cli.run import run
13
13
  from scry_run.cli.env import env
14
14
  from scry_run.cli.apps import list_apps, which_app, rm_app, reset_app, info_app
15
15
  from scry_run.cli.config_cmd import config_cmd
16
+ from scry_run.cli.log import log_cmd
16
17
  from scry_run.generator import CodeGenerator, ScryRunError
17
18
 
18
19
 
@@ -131,6 +132,7 @@ main.add_command(rm_app, name="rm")
131
132
  main.add_command(reset_app, name="reset")
132
133
  main.add_command(info_app, name="info")
133
134
  main.add_command(config_cmd, name="config")
135
+ main.add_command(log_cmd, name="log")
134
136
 
135
137
 
136
138
  if __name__ == "__main__":
@@ -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
- Return ONLY the expanded description text, no JSON or markdown code blocks.'''
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
- def expand_description(name: str, description: str) -> str | None:
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
- Expanded description, or None if expansion fails
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
- expanded = generator.generate_freeform(prompt)
76
- return expanded.strip()
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
@@ -241,10 +278,17 @@ def to_class_name(name: str) -> str:
241
278
  default=True,
242
279
  help="Automatically expand description with features and examples (default: enabled)",
243
280
  )
281
+ @click.option(
282
+ "--yes", "-y",
283
+ is_flag=True,
284
+ default=False,
285
+ help="Auto-approve all prompts (expanded description, overwrite existing app)",
286
+ )
244
287
  def init(
245
288
  name: str | None,
246
289
  description: str | None,
247
290
  auto_expand: bool,
291
+ yes: bool,
248
292
  ) -> None:
249
293
  """Initialize a new scry-run app.
250
294
 
@@ -279,41 +323,57 @@ def init(
279
323
  )
280
324
 
281
325
  # Auto-expand description using LLM
326
+ # Track recommended flags from LLM
327
+ recommended_flags: dict = {}
328
+
282
329
  if auto_expand:
283
330
  console.print()
284
331
  console.print("[dim]Expanding description with AI...[/dim]")
285
- expanded = expand_description(name, description)
332
+ result = expand_description(name, description)
333
+
334
+ if result:
335
+ expanded_desc = result["description"]
336
+ recommended_flags = result.get("flags", {})
286
337
 
287
- if expanded:
288
338
  # Show the expanded description
289
339
  console.print()
290
340
  panel = Panel(
291
- expanded,
341
+ expanded_desc,
292
342
  title="[bold]Expanded Description[/bold]",
293
343
  border_style="blue",
294
344
  )
295
345
  console.print(panel)
346
+
347
+ # Show recommended flags if any are non-default
348
+ if recommended_flags.get("quiet"):
349
+ console.print("[dim]Recommended: quiet mode (TUI app)[/dim]")
350
+
296
351
  console.print()
297
352
 
298
- # Let user confirm, edit, or reject
299
- console.print("[dim](y)es, (n)o, or (e)dit in $EDITOR[/dim]")
300
- choice = Prompt.ask(
301
- "Use this description?",
302
- choices=["y", "n", "e"],
303
- default="y",
304
- )
305
- if choice == "y":
306
- description = expanded
307
- elif choice == "e":
308
- # Open in editor
309
- edited = click.edit(expanded)
310
- if edited:
311
- description = edited.strip()
312
- console.print("[dim]Using edited description.[/dim]")
313
- else:
314
- console.print("[dim]Editor returned empty, using original.[/dim]")
353
+ # Auto-approve or let user confirm
354
+ if yes:
355
+ description = expanded_desc
315
356
  else:
316
- console.print("[dim]Using original description.[/dim]")
357
+ # Let user confirm, edit, or reject
358
+ console.print("[dim](y)es, (n)o, or (e)dit in $EDITOR[/dim]")
359
+ choice = Prompt.ask(
360
+ "Use this description?",
361
+ choices=["y", "n", "e"],
362
+ default="y",
363
+ )
364
+ if choice == "y":
365
+ description = expanded_desc
366
+ elif choice == "e":
367
+ # Open in editor
368
+ edited = click.edit(expanded_desc)
369
+ if edited:
370
+ description = edited.strip()
371
+ console.print("[dim]Using edited description.[/dim]")
372
+ else:
373
+ console.print("[dim]Editor returned empty, using original.[/dim]")
374
+ else:
375
+ console.print("[dim]Using original description.[/dim]")
376
+ recommended_flags = {} # Reset flags if user rejected expansion
317
377
  else:
318
378
  console.print("[dim]Using original description.[/dim]")
319
379
 
@@ -330,7 +390,7 @@ def init(
330
390
 
331
391
  # Check for existing app
332
392
  if app_dir.exists():
333
- if not Confirm.ask(f"[yellow]App '{name}' already exists[/yellow]. Overwrite?"):
393
+ if not yes and not Confirm.ask(f"[yellow]App '{name}' already exists[/yellow]. Overwrite?"):
334
394
  console.print("[yellow]Aborted.[/yellow]")
335
395
  raise SystemExit(0)
336
396
 
@@ -356,6 +416,15 @@ def init(
356
416
  cache_file = app_dir / "cache.json"
357
417
  cache_file.write_text(json.dumps({}))
358
418
 
419
+ # Create settings.json with default flags from LLM recommendation
420
+ settings = {
421
+ "default_flags": {
422
+ "quiet": recommended_flags.get("quiet", False),
423
+ }
424
+ }
425
+ settings_file = app_dir / "settings.json"
426
+ settings_file.write_text(json.dumps(settings, indent=2))
427
+
359
428
  # Create logs directory
360
429
  logs_dir = app_dir / "logs"
361
430
  logs_dir.mkdir(exist_ok=True)
@@ -365,7 +434,11 @@ def init(
365
434
  console.print()
366
435
  console.print(f" [dim]Location:[/dim] {app_dir}")
367
436
  console.print(f" [dim]Main file:[/dim] {app_file}")
437
+ console.print(f" [dim]Settings:[/dim] {settings_file}")
368
438
  console.print()
439
+ if recommended_flags.get("quiet"):
440
+ console.print("[dim]Quiet mode enabled (TUI app) - status shown in title bar.[/dim]")
441
+ console.print()
369
442
  console.print("[bold]Useful commands:[/bold]")
370
443
  console.print(f" [cyan]scry-run run {name}[/cyan] Run your app")
371
444
  console.print(f" [cyan]scry-run info {name}[/cyan] View app details and cache stats")