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.
- scry_run-0.2.0/.claude/settings.local.json +20 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/PKG-INFO +10 -5
- {scry_run-0.1.1 → scry_run-0.2.0}/README.md +8 -4
- {scry_run-0.1.1 → scry_run-0.2.0}/pyproject.toml +2 -1
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/__init__.py +1 -1
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/__init__.py +2 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/init.py +101 -28
- scry_run-0.2.0/src/scry_run/cli/log.py +373 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/run.py +39 -3
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/config.py +4 -6
- scry_run-0.2.0/src/scry_run/console.py +104 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/logging.py +12 -9
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/packages.py +20 -3
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cli.py +1 -1
- scry_run-0.2.0/tests/test_cli_log.py +175 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cli_run.py +64 -4
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_config.py +24 -7
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_generator.py +14 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/uv.lock +106 -1
- scry_run-0.1.1/.claude/settings.local.json +0 -12
- scry_run-0.1.1/src/scry_run/console.py +0 -59
- {scry_run-0.1.1 → scry_run-0.2.0}/.gitignore +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/__init__.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/base.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/claude.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/frozen.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/backends/registry.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cache.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/apps.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/cache.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/config_cmd.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/cli/env.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/context.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/generator.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/home.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/src/scry_run/meta.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/conftest.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cache.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cli_default.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_cli_env.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_context.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_home.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_integration.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_logging.py +0 -0
- {scry_run-0.1.1 → scry_run-0.2.0}/tests/test_meta.py +0 -0
- {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.
|
|
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
|
-
###
|
|
62
|
+
### Greet app
|
|
62
63
|
|
|
63
|
-
](demos/out/greet.webm)
|
|
64
65
|
|
|
65
|
-
###
|
|
66
|
+
### Timer app
|
|
66
67
|
|
|
67
|
-
](demos/out/timer.webm)
|
|
69
|
+
|
|
70
|
+
### Maze app
|
|
71
|
+
|
|
72
|
+
[](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
|
-
###
|
|
31
|
+
### Greet app
|
|
32
32
|
|
|
33
|
-
](demos/out/greet.webm)
|
|
34
34
|
|
|
35
|
-
###
|
|
35
|
+
### Timer app
|
|
36
36
|
|
|
37
|
-
](demos/out/timer.webm)
|
|
38
|
+
|
|
39
|
+
### Maze app
|
|
40
|
+
|
|
41
|
+
[](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.
|
|
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]
|
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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")
|