fluxloop-cli 0.2.3__tar.gz → 0.2.4__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.
Potentially problematic release.
This version of fluxloop-cli might be problematic. Click here for more details.
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/PKG-INFO +13 -1
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/README.md +12 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/__init__.py +1 -1
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/runner.py +40 -3
- fluxloop_cli-0.2.4/fluxloop_cli/target_loader.py +135 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli.egg-info/PKG-INFO +13 -1
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/pyproject.toml +1 -1
- fluxloop_cli-0.2.3/fluxloop_cli/target_loader.py +0 -95
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/arg_binder.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/commands/__init__.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/commands/config.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/commands/generate.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/commands/init.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/commands/parse.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/commands/record.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/commands/run.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/commands/status.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/config_loader.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/config_schema.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/constants.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/input_generator.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/llm_generator.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/main.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/project_paths.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/templates.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli/validators.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli.egg-info/entry_points.txt +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli.egg-info/requires.txt +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/fluxloop_cli.egg-info/top_level.txt +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/setup.cfg +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/tests/test_arg_binder.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/tests/test_config_command.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/tests/test_input_generator.py +0 -0
- {fluxloop_cli-0.2.3 → fluxloop_cli-0.2.4}/tests/test_target_loader.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fluxloop-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: FluxLoop CLI for running agent simulations
|
|
5
5
|
Author-email: FluxLoop Team <team@fluxloop.dev>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -71,6 +71,18 @@ The legacy `setting.yaml` is still supported, but new projects created with
|
|
|
71
71
|
|
|
72
72
|
Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
|
|
73
73
|
|
|
74
|
+
## Runner Integration Patterns
|
|
75
|
+
|
|
76
|
+
Configure how FluxLoop calls your code in `configs/simulation.yaml`:
|
|
77
|
+
|
|
78
|
+
- Module + function: `module_path`/`function_name` or `target: "module:function"`
|
|
79
|
+
- Class.method (zero-arg ctor): `target: "module:Class.method"`
|
|
80
|
+
- Module-scoped instance method: `target: "module:instance.method"`
|
|
81
|
+
- Class.method with factory: add `factory: "module:make_instance"` (+ `factory_kwargs`)
|
|
82
|
+
- Async generators: set `runner.stream_output_path` if your streamed event shape differs (default `message.delta`).
|
|
83
|
+
|
|
84
|
+
See full examples: `packages/website/docs-cli/configuration/runner-targets.md`.
|
|
85
|
+
|
|
74
86
|
## Developing
|
|
75
87
|
|
|
76
88
|
Install dependencies and run tests:
|
|
@@ -31,6 +31,18 @@ The legacy `setting.yaml` is still supported, but new projects created with
|
|
|
31
31
|
|
|
32
32
|
Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
|
|
33
33
|
|
|
34
|
+
## Runner Integration Patterns
|
|
35
|
+
|
|
36
|
+
Configure how FluxLoop calls your code in `configs/simulation.yaml`:
|
|
37
|
+
|
|
38
|
+
- Module + function: `module_path`/`function_name` or `target: "module:function"`
|
|
39
|
+
- Class.method (zero-arg ctor): `target: "module:Class.method"`
|
|
40
|
+
- Module-scoped instance method: `target: "module:instance.method"`
|
|
41
|
+
- Class.method with factory: add `factory: "module:make_instance"` (+ `factory_kwargs`)
|
|
42
|
+
- Async generators: set `runner.stream_output_path` if your streamed event shape differs (default `message.delta`).
|
|
43
|
+
|
|
44
|
+
See full examples: `packages/website/docs-cli/configuration/runner-targets.md`.
|
|
45
|
+
|
|
34
46
|
## Developing
|
|
35
47
|
|
|
36
48
|
Install dependencies and run tests:
|
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
8
|
import json
|
|
9
|
+
import inspect
|
|
9
10
|
import time
|
|
10
11
|
from datetime import datetime
|
|
11
12
|
from pathlib import Path
|
|
@@ -397,11 +398,20 @@ class ExperimentRunner:
|
|
|
397
398
|
if callable(error_cb) and hasattr(error_cb, "errors"):
|
|
398
399
|
callback_store["error"] = error_cb.errors
|
|
399
400
|
|
|
401
|
+
if inspect.isasyncgenfunction(agent_func):
|
|
402
|
+
return await self._consume_async_gen(agent_func, kwargs)
|
|
403
|
+
|
|
400
404
|
if asyncio.iscoroutinefunction(agent_func):
|
|
401
|
-
|
|
405
|
+
result = await agent_func(**kwargs)
|
|
406
|
+
else:
|
|
407
|
+
loop = asyncio.get_event_loop()
|
|
408
|
+
result = await loop.run_in_executor(None, lambda: agent_func(**kwargs))
|
|
402
409
|
|
|
403
|
-
|
|
404
|
-
|
|
410
|
+
# If an async generator/iterable is returned, consume it into a string
|
|
411
|
+
if inspect.isasyncgen(result) or hasattr(result, "__aiter__"):
|
|
412
|
+
return await self._consume_async_iterable(result)
|
|
413
|
+
|
|
414
|
+
return result
|
|
405
415
|
|
|
406
416
|
async def _wait_for_callbacks(
|
|
407
417
|
self,
|
|
@@ -464,6 +474,33 @@ class ExperimentRunner:
|
|
|
464
474
|
|
|
465
475
|
return None
|
|
466
476
|
|
|
477
|
+
async def _consume_async_gen(self, func: Callable, kwargs: Dict[str, Any]) -> Any:
|
|
478
|
+
"""Consume an async generator function by joining text chunks resolved from events."""
|
|
479
|
+
gen = func(**kwargs)
|
|
480
|
+
return await self._consume_async_iterable(gen)
|
|
481
|
+
|
|
482
|
+
async def _consume_async_iterable(self, agen: Any) -> Any:
|
|
483
|
+
"""Consume async iterable items, extracting text via runner.stream_output_path."""
|
|
484
|
+
path = (getattr(self.config.runner, "stream_output_path", None) or "message.delta").split(".")
|
|
485
|
+
chunks: List[str] = []
|
|
486
|
+
async for item in agen:
|
|
487
|
+
val = self._get_by_path(item, path)
|
|
488
|
+
if isinstance(val, str) and val:
|
|
489
|
+
chunks.append(val)
|
|
490
|
+
return "".join(chunks) if chunks else None
|
|
491
|
+
|
|
492
|
+
@staticmethod
|
|
493
|
+
def _get_by_path(obj: Any, parts: List[str]) -> Any:
|
|
494
|
+
cur: Any = obj
|
|
495
|
+
for key in parts:
|
|
496
|
+
if cur is None:
|
|
497
|
+
return None
|
|
498
|
+
if isinstance(cur, dict):
|
|
499
|
+
cur = cur.get(key)
|
|
500
|
+
else:
|
|
501
|
+
cur = getattr(cur, key, None)
|
|
502
|
+
return cur
|
|
503
|
+
|
|
467
504
|
@staticmethod
|
|
468
505
|
def _extract_payload(args: Sequence[Any], kwargs: Dict[str, Any]) -> Any:
|
|
469
506
|
if kwargs:
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Utilities for dynamically loading experiment targets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable, Optional
|
|
9
|
+
|
|
10
|
+
from fluxloop.schemas import RunnerConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TargetLoader:
|
|
14
|
+
"""Load callables defined by an experiment runner configuration."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, config: RunnerConfig, source_dir: Optional[Path] = None) -> None:
|
|
17
|
+
self.config = config
|
|
18
|
+
self.source_dir = source_dir
|
|
19
|
+
|
|
20
|
+
def load(self) -> Callable:
|
|
21
|
+
"""Return a callable based on the configured target."""
|
|
22
|
+
|
|
23
|
+
work_dir = self._resolve_working_directory()
|
|
24
|
+
remove_path = False
|
|
25
|
+
|
|
26
|
+
if work_dir and work_dir not in sys.path:
|
|
27
|
+
sys.path.insert(0, work_dir)
|
|
28
|
+
remove_path = True
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
if self.config.target:
|
|
32
|
+
return self._load_from_target(self.config.target)
|
|
33
|
+
|
|
34
|
+
module = importlib.import_module(self.config.module_path)
|
|
35
|
+
return getattr(module, self.config.function_name)
|
|
36
|
+
finally:
|
|
37
|
+
if remove_path:
|
|
38
|
+
sys.path.remove(work_dir)
|
|
39
|
+
|
|
40
|
+
def _resolve_working_directory(self) -> str | None:
|
|
41
|
+
if not self.config.working_directory:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
raw_path = Path(self.config.working_directory)
|
|
45
|
+
if not raw_path.is_absolute() and self.source_dir:
|
|
46
|
+
raw_path = (self.source_dir / raw_path).resolve()
|
|
47
|
+
else:
|
|
48
|
+
raw_path = raw_path.expanduser().resolve()
|
|
49
|
+
|
|
50
|
+
path = raw_path
|
|
51
|
+
return str(path)
|
|
52
|
+
|
|
53
|
+
def _load_from_target(self, target: str) -> Callable:
|
|
54
|
+
"""Resolve a callable from target string.
|
|
55
|
+
|
|
56
|
+
Supports:
|
|
57
|
+
- module:function
|
|
58
|
+
- module:variable
|
|
59
|
+
- module:Class.method
|
|
60
|
+
- module:variable.method
|
|
61
|
+
If the first attribute is a class, attempts to construct an instance via:
|
|
62
|
+
- runner.factory if provided (module:callable)
|
|
63
|
+
- zero-argument constructor fallback
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
if ":" not in target:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"Invalid runner.target format. Expected 'module:symbol[.attr]'."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
module_name, attribute_chain = target.split(":", 1)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
module = importlib.import_module(module_name)
|
|
75
|
+
except ImportError as exc:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Failed to import module '{module_name}' for target '{target}': {exc}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
parts = attribute_chain.split(".") if attribute_chain else []
|
|
81
|
+
if not parts:
|
|
82
|
+
raise ValueError(f"Invalid target '{target}': missing attribute path")
|
|
83
|
+
|
|
84
|
+
# Resolve first symbol from module
|
|
85
|
+
try:
|
|
86
|
+
obj: Any = getattr(module, parts[0])
|
|
87
|
+
except AttributeError as exc:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"Symbol '{parts[0]}' not found in module '{module_name}' for target '{target}'."
|
|
90
|
+
) from exc
|
|
91
|
+
|
|
92
|
+
# If it's a class, construct instance
|
|
93
|
+
if isinstance(obj, type):
|
|
94
|
+
factory = getattr(self.config, "factory", None)
|
|
95
|
+
factory_kwargs = getattr(self.config, "factory_kwargs", {}) or {}
|
|
96
|
+
if factory:
|
|
97
|
+
if ":" not in factory:
|
|
98
|
+
raise ValueError("runner.factory must be in 'module:callable' format")
|
|
99
|
+
fmod, fname = factory.split(":", 1)
|
|
100
|
+
try:
|
|
101
|
+
fac_mod = importlib.import_module(fmod)
|
|
102
|
+
fac = getattr(fac_mod, fname)
|
|
103
|
+
except Exception as exc:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"Failed to import factory '{factory}' for target '{target}': {exc}"
|
|
106
|
+
) from exc
|
|
107
|
+
try:
|
|
108
|
+
obj = fac(**factory_kwargs)
|
|
109
|
+
except Exception as exc:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"Factory '{factory}' failed to construct instance: {exc}"
|
|
112
|
+
) from exc
|
|
113
|
+
else:
|
|
114
|
+
try:
|
|
115
|
+
obj = obj()
|
|
116
|
+
except TypeError as exc:
|
|
117
|
+
raise ValueError(
|
|
118
|
+
"Cannot construct class without zero-argument constructor. "
|
|
119
|
+
"Provide runner.factory to construct the instance."
|
|
120
|
+
) from exc
|
|
121
|
+
|
|
122
|
+
# Traverse remaining attributes
|
|
123
|
+
for attr in parts[1:]:
|
|
124
|
+
try:
|
|
125
|
+
obj = getattr(obj, attr)
|
|
126
|
+
except AttributeError as exc:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"Attribute '{attr}' not found while resolving target '{target}'."
|
|
129
|
+
) from exc
|
|
130
|
+
|
|
131
|
+
if not callable(obj):
|
|
132
|
+
raise ValueError(f"Resolved target '{target}' is not callable.")
|
|
133
|
+
|
|
134
|
+
return obj
|
|
135
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fluxloop-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: FluxLoop CLI for running agent simulations
|
|
5
5
|
Author-email: FluxLoop Team <team@fluxloop.dev>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -71,6 +71,18 @@ The legacy `setting.yaml` is still supported, but new projects created with
|
|
|
71
71
|
|
|
72
72
|
Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
|
|
73
73
|
|
|
74
|
+
## Runner Integration Patterns
|
|
75
|
+
|
|
76
|
+
Configure how FluxLoop calls your code in `configs/simulation.yaml`:
|
|
77
|
+
|
|
78
|
+
- Module + function: `module_path`/`function_name` or `target: "module:function"`
|
|
79
|
+
- Class.method (zero-arg ctor): `target: "module:Class.method"`
|
|
80
|
+
- Module-scoped instance method: `target: "module:instance.method"`
|
|
81
|
+
- Class.method with factory: add `factory: "module:make_instance"` (+ `factory_kwargs`)
|
|
82
|
+
- Async generators: set `runner.stream_output_path` if your streamed event shape differs (default `message.delta`).
|
|
83
|
+
|
|
84
|
+
See full examples: `packages/website/docs-cli/configuration/runner-targets.md`.
|
|
85
|
+
|
|
74
86
|
## Developing
|
|
75
87
|
|
|
76
88
|
Install dependencies and run tests:
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"""Utilities for dynamically loading experiment targets."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import importlib
|
|
6
|
-
import sys
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Callable, Optional
|
|
9
|
-
|
|
10
|
-
from fluxloop.schemas import RunnerConfig
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TargetLoader:
|
|
14
|
-
"""Load callables defined by an experiment runner configuration."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, config: RunnerConfig, source_dir: Optional[Path] = None) -> None:
|
|
17
|
-
self.config = config
|
|
18
|
-
self.source_dir = source_dir
|
|
19
|
-
|
|
20
|
-
def load(self) -> Callable:
|
|
21
|
-
"""Return a callable based on the configured target."""
|
|
22
|
-
|
|
23
|
-
work_dir = self._resolve_working_directory()
|
|
24
|
-
remove_path = False
|
|
25
|
-
|
|
26
|
-
if work_dir and work_dir not in sys.path:
|
|
27
|
-
sys.path.insert(0, work_dir)
|
|
28
|
-
remove_path = True
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
if self.config.target:
|
|
32
|
-
return self._load_from_target(self.config.target)
|
|
33
|
-
|
|
34
|
-
module = importlib.import_module(self.config.module_path)
|
|
35
|
-
return getattr(module, self.config.function_name)
|
|
36
|
-
finally:
|
|
37
|
-
if remove_path:
|
|
38
|
-
sys.path.remove(work_dir)
|
|
39
|
-
|
|
40
|
-
def _resolve_working_directory(self) -> str | None:
|
|
41
|
-
if not self.config.working_directory:
|
|
42
|
-
return None
|
|
43
|
-
|
|
44
|
-
raw_path = Path(self.config.working_directory)
|
|
45
|
-
if not raw_path.is_absolute() and self.source_dir:
|
|
46
|
-
raw_path = (self.source_dir / raw_path).resolve()
|
|
47
|
-
else:
|
|
48
|
-
raw_path = raw_path.expanduser().resolve()
|
|
49
|
-
|
|
50
|
-
path = raw_path
|
|
51
|
-
return str(path)
|
|
52
|
-
|
|
53
|
-
def _load_from_target(self, target: str) -> Callable:
|
|
54
|
-
if ":" not in target:
|
|
55
|
-
raise ValueError(
|
|
56
|
-
"Invalid runner.target format. Expected 'module:function' or 'module:Class.method'."
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
module_name, attribute_part = target.split(":", 1)
|
|
60
|
-
|
|
61
|
-
try:
|
|
62
|
-
module = importlib.import_module(module_name)
|
|
63
|
-
except ImportError as exc:
|
|
64
|
-
raise ValueError(f"Failed to import module '{module_name}' for target '{target}': {exc}")
|
|
65
|
-
|
|
66
|
-
if "." in attribute_part:
|
|
67
|
-
class_name, method_name = attribute_part.split(".", 1)
|
|
68
|
-
try:
|
|
69
|
-
cls = getattr(module, class_name)
|
|
70
|
-
except AttributeError as exc:
|
|
71
|
-
raise ValueError(
|
|
72
|
-
f"Class '{class_name}' not found in module '{module_name}' for target '{target}'."
|
|
73
|
-
) from exc
|
|
74
|
-
|
|
75
|
-
try:
|
|
76
|
-
instance = cls()
|
|
77
|
-
except TypeError as exc:
|
|
78
|
-
raise ValueError(
|
|
79
|
-
"MVP limitation: only classes with zero-argument constructors are supported."
|
|
80
|
-
) from exc
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
return getattr(instance, method_name)
|
|
84
|
-
except AttributeError as exc:
|
|
85
|
-
raise ValueError(
|
|
86
|
-
f"Method '{method_name}' not found on class '{class_name}' for target '{target}'."
|
|
87
|
-
) from exc
|
|
88
|
-
|
|
89
|
-
try:
|
|
90
|
-
return getattr(module, attribute_part)
|
|
91
|
-
except AttributeError as exc:
|
|
92
|
-
raise ValueError(
|
|
93
|
-
f"Function or attribute '{attribute_part}' not found in module '{module_name}' for target '{target}'."
|
|
94
|
-
) from exc
|
|
95
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|