lightnow-cli 1.7.0__tar.gz → 1.7.1__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.
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/.github/workflows/ci.yml +2 -2
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/.github/workflows/release.yml +3 -3
- lightnow_cli-1.7.1/.release-please-manifest.json +3 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/CHANGELOG.md +7 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/PKG-INFO +1 -1
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/__init__.py +1 -1
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/integrations.py +31 -2
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/pyproject.toml +1 -1
- lightnow_cli-1.7.1/tests/test_import_preconditions.py +197 -0
- lightnow_cli-1.7.0/.release-please-manifest.json +0 -3
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/.github/workflows/release-please.yml +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/.gitignore +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/CONTRIBUTING.md +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/LICENSE +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/Makefile +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/README.md +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/SECURITY.md +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/ci/create_client_artifact.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/ci/test_client_artifact.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/docs/development.md +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/docs/release.md +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/examples/README.md +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/examples/docs.md +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/examples/openapi.json +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/examples/server.json +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/authenticated_http.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/client.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/__init__.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/auth.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/context.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/publish.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/query.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/runner.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/updates.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/commands/validate.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/config.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/main.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/target.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/tls.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/updates.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/lightnow_cli/validation.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/release-please-config.json +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/renovate.json +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/__init__.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_auth_flow.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_authenticated_http.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_cli.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_client.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_config.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_connection_target.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_context.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_integrations.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_tls.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_updates.py +0 -0
- {lightnow_cli-1.7.0 → lightnow_cli-1.7.1}/tests/test_validation.py +0 -0
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
27
27
|
|
|
28
28
|
- name: Set up Python
|
|
29
|
-
uses: actions/setup-python@
|
|
29
|
+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
|
|
30
30
|
with:
|
|
31
31
|
python-version: ${{ matrix.python-version }}
|
|
32
32
|
cache: pip
|
|
@@ -62,7 +62,7 @@ jobs:
|
|
|
62
62
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
63
63
|
|
|
64
64
|
- name: Set up Python
|
|
65
|
-
uses: actions/setup-python@
|
|
65
|
+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
|
|
66
66
|
with:
|
|
67
67
|
python-version: "3.12"
|
|
68
68
|
cache: pip
|
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
19
19
|
|
|
20
20
|
- name: Set up Python
|
|
21
|
-
uses: actions/setup-python@
|
|
21
|
+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
|
|
22
22
|
with:
|
|
23
23
|
python-version: "3.12"
|
|
24
24
|
cache: pip
|
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
39
39
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
40
40
|
|
|
41
41
|
- name: Set up Python
|
|
42
|
-
uses: actions/setup-python@
|
|
42
|
+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
|
|
43
43
|
with:
|
|
44
44
|
python-version: "3.12"
|
|
45
45
|
cache: pip
|
|
@@ -95,7 +95,7 @@ jobs:
|
|
|
95
95
|
path: dist
|
|
96
96
|
|
|
97
97
|
- name: Publish distributions to PyPI
|
|
98
|
-
uses: pypa/gh-action-pypi-publish@
|
|
98
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
|
|
99
99
|
|
|
100
100
|
update-homebrew:
|
|
101
101
|
name: Update Homebrew formula
|
|
@@ -4,6 +4,13 @@ All notable changes to the LightNow CLI are documented here.
|
|
|
4
4
|
|
|
5
5
|
This project follows semantic versioning.
|
|
6
6
|
|
|
7
|
+
## [1.7.1](https://github.com/lightnow-ai/lightnow-cli/compare/v1.7.0...v1.7.1) (2026-11-14)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* honor profile revisions when importing MCP config ([#50](https://github.com/lightnow-ai/lightnow-cli/issues/50)) ([343ab0d](https://github.com/lightnow-ai/lightnow-cli/commit/343ab0d545c19d3c9a44fb9097ed06e5f00ca3fb))
|
|
13
|
+
|
|
7
14
|
## [1.7.0](https://github.com/lightnow-ai/lightnow-cli/compare/v1.6.1...v1.7.0) (2026-11-13)
|
|
8
15
|
|
|
9
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lightnow-cli
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.1
|
|
4
4
|
Summary: Official LightNow CLI for MCP registry discovery, client config sync, and local MCP runner workflows
|
|
5
5
|
Project-URL: Homepage, https://github.com/lightnow-ai/lightnow-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/lightnow-ai/lightnow-cli
|
|
@@ -898,7 +898,9 @@ def import_config(
|
|
|
898
898
|
] = "default",
|
|
899
899
|
tenant: Annotated[
|
|
900
900
|
Optional[str],
|
|
901
|
-
typer.Option(
|
|
901
|
+
typer.Option(
|
|
902
|
+
"--tenant", help="Organization ID; defaults to the selected context"
|
|
903
|
+
),
|
|
902
904
|
] = None,
|
|
903
905
|
config_path: Annotated[
|
|
904
906
|
Optional[Path],
|
|
@@ -1699,7 +1701,34 @@ def import_profile_config(
|
|
|
1699
1701
|
) -> dict[str, Any]:
|
|
1700
1702
|
"""Import rendered client config into a LightNow runtime profile."""
|
|
1701
1703
|
url = f"{api_url.rstrip('/')}/integrations/import"
|
|
1704
|
+
headers = {"Accept": "application/json"}
|
|
1702
1705
|
try:
|
|
1706
|
+
if not dry_run:
|
|
1707
|
+
current = request_with_refresh(
|
|
1708
|
+
"GET",
|
|
1709
|
+
f"{api_url.rstrip('/')}/integrations/profiles",
|
|
1710
|
+
headers=headers,
|
|
1711
|
+
token=token,
|
|
1712
|
+
tenant=tenant,
|
|
1713
|
+
timeout=30.0,
|
|
1714
|
+
)
|
|
1715
|
+
if current.status_code == 401:
|
|
1716
|
+
raise authentication_error_from_response(current)
|
|
1717
|
+
if current.status_code != 200:
|
|
1718
|
+
raise ValueError(f"HTTP {current.status_code}: {redact(current.text)}")
|
|
1719
|
+
etag = current.headers.get("ETag")
|
|
1720
|
+
if (
|
|
1721
|
+
etag is None
|
|
1722
|
+
or re.fullmatch(r'"[\x21\x23-\x7e\x80-\xff]*"', etag) is None
|
|
1723
|
+
):
|
|
1724
|
+
raise ValueError(
|
|
1725
|
+
"Could not read the current profile version. No import was applied."
|
|
1726
|
+
)
|
|
1727
|
+
headers["If-Match"] = etag
|
|
1728
|
+
# The snapshot request may have refreshed an expired session.
|
|
1729
|
+
authorization = current.request.headers.get("Authorization", "")
|
|
1730
|
+
if authorization.startswith("Bearer "):
|
|
1731
|
+
token = authorization.removeprefix("Bearer ")
|
|
1703
1732
|
response = request_with_refresh(
|
|
1704
1733
|
"POST",
|
|
1705
1734
|
url,
|
|
@@ -1710,7 +1739,7 @@ def import_profile_config(
|
|
|
1710
1739
|
"profile": {"name": profile},
|
|
1711
1740
|
"replace": replace,
|
|
1712
1741
|
},
|
|
1713
|
-
headers=
|
|
1742
|
+
headers=headers,
|
|
1714
1743
|
token=token,
|
|
1715
1744
|
tenant=tenant,
|
|
1716
1745
|
timeout=30.0,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lightnow-cli"
|
|
7
|
-
version = "1.7.
|
|
7
|
+
version = "1.7.1"
|
|
8
8
|
description = "Official LightNow CLI for MCP registry discovery, client config sync, and local MCP runner workflows"
|
|
9
9
|
authors = [{name = "LightNow Team"}]
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Import writes retain the API's optimistic concurrency and permission checks."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from lightnow_cli.commands.integrations import import_profile_config
|
|
10
|
+
|
|
11
|
+
API = "https://registry.example.test/v0.1"
|
|
12
|
+
CONTENT = '[mcp_servers.example]\ncommand = "example-mcp"\n'
|
|
13
|
+
ETAG = '"owner-snapshot-1"'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def import_config(
|
|
17
|
+
*, dry_run=False, tenant="organization-id", profile="team", replace=False
|
|
18
|
+
):
|
|
19
|
+
return import_profile_config(
|
|
20
|
+
api_url=API,
|
|
21
|
+
token="test-access-token",
|
|
22
|
+
tenant=tenant,
|
|
23
|
+
source="codex",
|
|
24
|
+
content=CONTENT,
|
|
25
|
+
profile=profile,
|
|
26
|
+
dry_run=dry_run,
|
|
27
|
+
replace=replace,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"tenant,profile", [(None, "default"), ("organization-id", "team")]
|
|
33
|
+
)
|
|
34
|
+
@pytest.mark.parametrize("replace", [False, True])
|
|
35
|
+
def test_apply_reads_owner_snapshot_and_preserves_import_scope(
|
|
36
|
+
tenant, profile, replace
|
|
37
|
+
):
|
|
38
|
+
requests = []
|
|
39
|
+
|
|
40
|
+
def handle(request):
|
|
41
|
+
requests.append(request)
|
|
42
|
+
assert request.headers["Authorization"] == "Bearer test-access-token"
|
|
43
|
+
assert request.headers.get("X-Tenant") == tenant
|
|
44
|
+
if request.method == "GET":
|
|
45
|
+
assert request.url == f"{API}/integrations/profiles"
|
|
46
|
+
assert not request.content
|
|
47
|
+
assert "If-Match" not in request.headers
|
|
48
|
+
return httpx.Response(200, headers={"ETag": ETAG}, json={"profiles": []})
|
|
49
|
+
assert request.method == "POST"
|
|
50
|
+
assert request.url == f"{API}/integrations/import?dry_run=false"
|
|
51
|
+
assert request.headers["If-Match"] == ETAG
|
|
52
|
+
assert json.loads(request.content) == {
|
|
53
|
+
"source": "codex",
|
|
54
|
+
"content": CONTENT,
|
|
55
|
+
"profile": {"name": profile},
|
|
56
|
+
"replace": replace,
|
|
57
|
+
}
|
|
58
|
+
return httpx.Response(200, json={"dry_run": False})
|
|
59
|
+
|
|
60
|
+
with (
|
|
61
|
+
httpx.Client(transport=httpx.MockTransport(handle)) as client,
|
|
62
|
+
patch(
|
|
63
|
+
"lightnow_cli.authenticated_http.httpx.request", side_effect=client.request
|
|
64
|
+
),
|
|
65
|
+
):
|
|
66
|
+
assert import_config(tenant=tenant, profile=profile, replace=replace) == {
|
|
67
|
+
"dry_run": False
|
|
68
|
+
}
|
|
69
|
+
assert [request.method for request in requests] == ["GET", "POST"]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_preview_does_not_fetch_or_send_a_write_precondition():
|
|
73
|
+
requests = []
|
|
74
|
+
|
|
75
|
+
def handle(request):
|
|
76
|
+
requests.append(request)
|
|
77
|
+
assert request.method == "POST"
|
|
78
|
+
assert request.url.params["dry_run"] == "true"
|
|
79
|
+
assert "If-Match" not in request.headers
|
|
80
|
+
return httpx.Response(200, json={"dry_run": True})
|
|
81
|
+
|
|
82
|
+
with (
|
|
83
|
+
httpx.Client(transport=httpx.MockTransport(handle)) as client,
|
|
84
|
+
patch(
|
|
85
|
+
"lightnow_cli.authenticated_http.httpx.request", side_effect=client.request
|
|
86
|
+
),
|
|
87
|
+
):
|
|
88
|
+
assert import_config(dry_run=True) == {"dry_run": True}
|
|
89
|
+
assert len(requests) == 1
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize(
|
|
93
|
+
"etag", [None, "", "*", 'W/"weak"', "unquoted", '"one", "two"']
|
|
94
|
+
)
|
|
95
|
+
def test_missing_or_invalid_snapshot_never_sends_import(etag):
|
|
96
|
+
requests = []
|
|
97
|
+
|
|
98
|
+
def handle(request):
|
|
99
|
+
requests.append(request)
|
|
100
|
+
assert request.method == "GET"
|
|
101
|
+
return httpx.Response(
|
|
102
|
+
200,
|
|
103
|
+
headers={"ETag": etag} if etag is not None else {},
|
|
104
|
+
json={"profiles": []},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
with (
|
|
108
|
+
httpx.Client(transport=httpx.MockTransport(handle)) as client,
|
|
109
|
+
patch(
|
|
110
|
+
"lightnow_cli.authenticated_http.httpx.request", side_effect=client.request
|
|
111
|
+
),
|
|
112
|
+
pytest.raises(ValueError, match="current profile version"),
|
|
113
|
+
):
|
|
114
|
+
import_config()
|
|
115
|
+
assert len(requests) == 1
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.mark.parametrize("status", [302, 403, 503])
|
|
119
|
+
def test_failed_snapshot_read_never_sends_import(status):
|
|
120
|
+
requests = []
|
|
121
|
+
|
|
122
|
+
def handle(request):
|
|
123
|
+
requests.append(request)
|
|
124
|
+
assert request.method == "GET"
|
|
125
|
+
return httpx.Response(
|
|
126
|
+
status,
|
|
127
|
+
headers={"ETag": ETAG},
|
|
128
|
+
json={"error": {"message": "Cannot read profiles"}},
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
with (
|
|
132
|
+
httpx.Client(transport=httpx.MockTransport(handle)) as client,
|
|
133
|
+
patch(
|
|
134
|
+
"lightnow_cli.authenticated_http.httpx.request", side_effect=client.request
|
|
135
|
+
),
|
|
136
|
+
pytest.raises(ValueError, match=f"HTTP {status}"),
|
|
137
|
+
):
|
|
138
|
+
import_config()
|
|
139
|
+
assert len(requests) == 1
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_conflict_stops_without_refetch_or_reapply():
|
|
143
|
+
requests = []
|
|
144
|
+
|
|
145
|
+
def handle(request):
|
|
146
|
+
requests.append(request)
|
|
147
|
+
if request.method == "GET":
|
|
148
|
+
return httpx.Response(200, headers={"ETag": ETAG}, json={"profiles": []})
|
|
149
|
+
assert request.headers["If-Match"] == ETAG
|
|
150
|
+
return httpx.Response(412, json={"message": "Profile changed"})
|
|
151
|
+
|
|
152
|
+
with (
|
|
153
|
+
httpx.Client(transport=httpx.MockTransport(handle)) as client,
|
|
154
|
+
patch(
|
|
155
|
+
"lightnow_cli.authenticated_http.httpx.request", side_effect=client.request
|
|
156
|
+
),
|
|
157
|
+
pytest.raises(ValueError, match="HTTP 412"),
|
|
158
|
+
):
|
|
159
|
+
import_config()
|
|
160
|
+
assert [request.method for request in requests] == ["GET", "POST"]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@pytest.mark.parametrize("expired_method", ["GET", "POST"])
|
|
164
|
+
def test_existing_session_refresh_preserves_scope_and_write_snapshot(expired_method):
|
|
165
|
+
requests = []
|
|
166
|
+
expired = False
|
|
167
|
+
|
|
168
|
+
def handle(request):
|
|
169
|
+
nonlocal expired
|
|
170
|
+
requests.append(request)
|
|
171
|
+
assert request.headers["X-Tenant"] == "organization-id"
|
|
172
|
+
if request.method == "POST":
|
|
173
|
+
assert request.headers["If-Match"] == ETAG
|
|
174
|
+
if request.method == expired_method and not expired:
|
|
175
|
+
expired = True
|
|
176
|
+
return httpx.Response(401, json={"error": "token_expired"})
|
|
177
|
+
if expired:
|
|
178
|
+
assert request.headers["Authorization"] == "Bearer refreshed-test-token"
|
|
179
|
+
if request.method == "GET":
|
|
180
|
+
return httpx.Response(200, headers={"ETag": ETAG}, json={"profiles": []})
|
|
181
|
+
return httpx.Response(200, json={"dry_run": False})
|
|
182
|
+
|
|
183
|
+
with (
|
|
184
|
+
httpx.Client(transport=httpx.MockTransport(handle)) as client,
|
|
185
|
+
patch(
|
|
186
|
+
"lightnow_cli.authenticated_http.httpx.request", side_effect=client.request
|
|
187
|
+
),
|
|
188
|
+
patch(
|
|
189
|
+
"lightnow_cli.authenticated_http.refresh_current_session",
|
|
190
|
+
return_value="refreshed-test-token",
|
|
191
|
+
) as refresh,
|
|
192
|
+
):
|
|
193
|
+
assert import_config() == {"dry_run": False}
|
|
194
|
+
assert refresh.call_count == 1
|
|
195
|
+
assert [request.method for request in requests] == (
|
|
196
|
+
["GET", "GET", "POST"] if expired_method == "GET" else ["GET", "POST", "POST"]
|
|
197
|
+
)
|
|
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
|
|
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
|