mcpsmith 0.2.1__tar.gz → 0.2.2__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.
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/.claude-plugin/plugin.json +2 -2
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/PKG-INFO +1 -1
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/pyproject.toml +1 -1
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/server.json +2 -2
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/skills/design-mcp-tools/SKILL.md +1 -1
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/skills/extract-api/SKILL.md +1 -1
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/__init__.py +1 -1
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/design/_validate.py +7 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/design/guide.md +4 -2
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/model.py +4 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/runtime/http.py +11 -4
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/runtime/interpret.py +5 -1
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_design.py +10 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_runtime.py +23 -2
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/uv.lock +1 -1
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/.claude-plugin/marketplace.json +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/.github/workflows/ci.yml +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/.github/workflows/publish.yml +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/.gitignore +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/AGENTS.md +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/LICENSE +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/README.md +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/docs/design.md +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/docs/superpowers/plans/2026-10-21-hub-core.md +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/docs/superpowers/specs/2026-10-20-mcp-everything-design.md +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/skills/build-mcp-server/SKILL.md +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/browser.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/capture.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/check.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/cli.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/codegen.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/design/__init__.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/design/_digest.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/design/_draft.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/design/llm.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/hub.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/importers/__init__.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/importers/har.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/importers/openapi.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/importers/rea.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/record.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/runtime/__init__.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/runtime/server.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/runtime/shape.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/runtime/transforms.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/secrets.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/server.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/templates/ci.yml +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/templates/conftest.py.tmpl +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/templates/gitignore +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/src/mcpsmith/templates/publish.yml +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/conftest.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/fake_cdp.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/fixtures/har/shop.har +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/fixtures/openapi/petstore2.yaml +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/fixtures/openapi/petstore3.json +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/fixtures/openapi/tasks31.json +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/fixtures/rea/scenario.json +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_browser.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_cli.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_codegen.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_hub.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_importers.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_interpret.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_record.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_secrets.py +0 -0
- {mcpsmith-0.2.1 → mcpsmith-0.2.2}/tests/test_transforms.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcpsmith",
|
|
3
3
|
"displayName": "mcpsmith",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"description": "Turn an OpenAPI file or a recorded website session into a curated, tested MCP server. Skills and an MCP server for your coding agent.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sepehr Radmard",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"mcpsmith": {
|
|
24
24
|
"command": "uvx",
|
|
25
25
|
"args": [
|
|
26
|
-
"mcpsmith@0.2.
|
|
26
|
+
"mcpsmith@0.2.2",
|
|
27
27
|
"mcp"
|
|
28
28
|
]
|
|
29
29
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mcpsmith
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Turn an OpenAPI file or a recorded website session into a curated, tested MCP server, with an LLM designing the tools.
|
|
5
5
|
Project-URL: Homepage, https://github.com/sepehr071/mcpsmith
|
|
6
6
|
Project-URL: Issues, https://github.com/sepehr071/mcpsmith/issues
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
10
|
"websiteUrl": "https://github.com/sepehr071/mcpsmith",
|
|
11
|
-
"version": "0.2.
|
|
11
|
+
"version": "0.2.2",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "pypi",
|
|
15
15
|
"registryBaseUrl": "https://pypi.org",
|
|
16
16
|
"identifier": "mcpsmith",
|
|
17
|
-
"version": "0.2.
|
|
17
|
+
"version": "0.2.2",
|
|
18
18
|
"runtimeHint": "uvx",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|
|
@@ -49,7 +49,7 @@ Safety and server
|
|
|
49
49
|
- `example` for every tool with required args; real values that return data.
|
|
50
50
|
- `instructions`: the cross-tool workflow ("search, then details by id"), units, currency, language, rate limits, limits of the data.
|
|
51
51
|
- Secrets only via env vars (`<P>_...`) or `mcpsmith secret set`, never in args or descriptions.
|
|
52
|
-
- Site that needs the user's login: `auth: {type: browser, login_path: /login}` (hub only; `login_path` is where the site redirects when logged out). `generate` refuses it: switch to token auth to export.
|
|
52
|
+
- Site that needs the user's login: `auth: {type: browser, login_path: /login}` (hub only; `login_path` is where the site redirects when logged out). Also set `session_cookie`, the login cookie's name: compare the cookie NAMES Chrome holds logged in vs logged out (`mcpsmith.browser.cookies_for`, names only, never print values). Some sites answer an ended session with HTTP 200 and an empty body. `generate` refuses it: switch to token auth to export.
|
|
53
53
|
|
|
54
54
|
## Measure
|
|
55
55
|
|
|
@@ -49,7 +49,7 @@ Read the digest and the guide fully, then edit `<workdir>/plan.yaml` yourself (s
|
|
|
49
49
|
- Keep only tools for the goals from step 1; drop analytics, menus, config, auth plumbing.
|
|
50
50
|
- Rename cryptic fields and args to what a person says (`emp_id` to `employee_id`, `Item_dev_sort_date` through `map: {newest: ...}`).
|
|
51
51
|
- Add `transform` on args and `output.transforms` for dates, unix times, minutes, currency. Set `timezone` (IANA name; empty means the machine's local zone).
|
|
52
|
-
- Set `auth: {type: browser, login_path: /Account/Login}`; `login_path` is the path the site redirects to when logged out.
|
|
52
|
+
- Set `auth: {type: browser, login_path: /Account/Login}`; `login_path` is the path the site redirects to when logged out. Also set `session_cookie` to the login cookie's name: compare the cookie NAMES Chrome holds logged in vs logged out (`mcpsmith.browser.cookies_for`, names only, never print values). Some sites answer an ended session with HTTP 200 and an empty body.
|
|
53
53
|
- Fill `example` for every tool with values seen in the recording; `read_only: false` for anything that writes.
|
|
54
54
|
|
|
55
55
|
```
|
|
@@ -119,6 +119,13 @@ def validate(plan: Plan, source: Source | None = None) -> list[Issue]:
|
|
|
119
119
|
check("plan.timezone", check_timezone, plan.timezone)
|
|
120
120
|
if plan.auth and plan.auth.type == "browser":
|
|
121
121
|
warn("plan.auth", "browser auth needs the hub's Chrome; switch to token auth before exporting.")
|
|
122
|
+
if not plan.auth.session_cookie:
|
|
123
|
+
warn(
|
|
124
|
+
"plan.auth.session_cookie",
|
|
125
|
+
"set auth.session_cookie so an ended session is reported instead of returning empty data.",
|
|
126
|
+
)
|
|
127
|
+
elif plan.auth and plan.auth.session_cookie:
|
|
128
|
+
err("plan.auth.session_cookie", "session_cookie only applies to browser auth.")
|
|
122
129
|
if len(plan.tools) > 20:
|
|
123
130
|
warn("plan.tools", f"{len(plan.tools)} tools; merge near-duplicates and drop noise to stay at 20 or fewer.")
|
|
124
131
|
|
|
@@ -89,10 +89,12 @@ Raw API responses waste the agent's context and bury the answer. For every tool:
|
|
|
89
89
|
|
|
90
90
|
Plan: `name` (e.g. `petstore-mcp`), `title`, `description` (<= 100 chars), `instructions`, `prefix` (tool name
|
|
91
91
|
prefix, e.g. `pet`), `version`, `base_url`, `env_prefix` (upper case, `^[A-Z][A-Z0-9_]*$`, ending in `_MCP`; `auth.env` must start with it; e.g.
|
|
92
|
-
`PETSTORE_MCP`), `headers`, `auth` (`{name, type: bearer|header|query|cookie|basic|browser, param, env, login_path}`;
|
|
92
|
+
`PETSTORE_MCP`), `headers`, `auth` (`{name, type: bearer|header|query|cookie|basic|browser, param, env, login_path, session_cookie}`;
|
|
93
93
|
`browser` and `login_path` only work in the hub, not in exported servers: `browser` reuses the developer's
|
|
94
94
|
logged-in shared Chrome session, `login_path` is the path prefix the site redirects to when logged out, e.g.
|
|
95
|
-
`/login`; `
|
|
95
|
+
`/login`; `session_cookie` is the name of the cookie that proves the login (some sites answer an ended session with
|
|
96
|
+
HTTP 200 and an empty body, so no redirect shows it; find it by comparing the cookie names Chrome holds logged in vs
|
|
97
|
+
logged out, never their values); `generate` refuses browser-auth plans, switch to bearer/header/cookie auth to export),
|
|
96
98
|
`impersonate`, `concurrency`, `cache_ttl`, `timezone` (IANA name for date transforms; empty = system local),
|
|
97
99
|
`author`, `repository`, `tools`.
|
|
98
100
|
|
|
@@ -150,6 +150,10 @@ class AuthScheme(BaseModel):
|
|
|
150
150
|
login_path: str = Field(
|
|
151
151
|
"", description="Browser auth: path prefix whose redirect means logged out, e.g. '/Account/Login'."
|
|
152
152
|
)
|
|
153
|
+
session_cookie: str = Field(
|
|
154
|
+
"",
|
|
155
|
+
description="Browser auth: name of the cookie that proves the user is logged in; absent means session ended.",
|
|
156
|
+
)
|
|
153
157
|
|
|
154
158
|
@field_validator("env")
|
|
155
159
|
@classmethod
|
|
@@ -91,6 +91,7 @@ class Api:
|
|
|
91
91
|
secret: Callable[[str], str | None] | None = None,
|
|
92
92
|
cookie_provider: Callable[[str], Awaitable[dict[str, str]]] | None = None,
|
|
93
93
|
login_path: str = "",
|
|
94
|
+
session_cookie: str = "",
|
|
94
95
|
proxy: str | None = None,
|
|
95
96
|
):
|
|
96
97
|
if auth and (auth.get("type") not in AUTH_TYPES or not (auth.get("env") or auth.get("type") == "browser")):
|
|
@@ -108,6 +109,7 @@ class Api:
|
|
|
108
109
|
self.secret = secret
|
|
109
110
|
self.cookie_provider = cookie_provider
|
|
110
111
|
self.login_path = login_path
|
|
112
|
+
self.session_cookie = session_cookie
|
|
111
113
|
self.proxy = proxy
|
|
112
114
|
self._transport: httpx.AsyncBaseTransport | None = None
|
|
113
115
|
self._client: httpx.AsyncClient | None = None
|
|
@@ -149,11 +151,14 @@ class Api:
|
|
|
149
151
|
sent = self._add_auth(query, headers, cookies)
|
|
150
152
|
if self.auth and self.auth["type"] == "browser" and self.cookie_provider:
|
|
151
153
|
try:
|
|
152
|
-
|
|
154
|
+
jar = await self.cookie_provider(host)
|
|
153
155
|
except (OSError, httpx.HTTPError, RuntimeError):
|
|
154
156
|
raise ApiError(
|
|
155
157
|
"The mcpsmith Chrome is not running. Start it with `mcpsmith browser`, log in, then retry."
|
|
156
158
|
) from None
|
|
159
|
+
if self.session_cookie and self.session_cookie not in jar:
|
|
160
|
+
raise ApiError(self._expired_message(host))
|
|
161
|
+
cookies.update(jar)
|
|
157
162
|
sent = True
|
|
158
163
|
if any(re.search(r"[;\r\n]", str(v)) for v in cookies.values()) or any(
|
|
159
164
|
re.search(r"[\r\n]", str(v)) for v in headers.values()
|
|
@@ -186,9 +191,7 @@ class Api:
|
|
|
186
191
|
break
|
|
187
192
|
|
|
188
193
|
if self.auth and self.auth["type"] == "browser" and sent and self._expired(r):
|
|
189
|
-
raise ApiError(
|
|
190
|
-
f"{host} says the session has expired. Log in again in the mcpsmith Chrome window, then retry."
|
|
191
|
-
)
|
|
194
|
+
raise ApiError(self._expired_message(host))
|
|
192
195
|
if r.status_code >= 300:
|
|
193
196
|
raise ApiError(self._status_message(r, content, host, sent), r.status_code)
|
|
194
197
|
if not content:
|
|
@@ -204,6 +207,10 @@ class Api:
|
|
|
204
207
|
self._cache[key] = (time.monotonic(), copy.deepcopy(data))
|
|
205
208
|
return data
|
|
206
209
|
|
|
210
|
+
@staticmethod
|
|
211
|
+
def _expired_message(host: str) -> str:
|
|
212
|
+
return f"{host} says the session has expired. Log in again in the mcpsmith Chrome window, then retry."
|
|
213
|
+
|
|
207
214
|
def _expired(self, r: httpx.Response) -> bool:
|
|
208
215
|
"""A 401, or a redirect to the site's login page."""
|
|
209
216
|
loc = httpx.URL(r.headers.get("location", "")).path
|
|
@@ -65,7 +65,11 @@ def make_api(plan: Plan, source: Source | None, *, proxy: str | None = None, coo
|
|
|
65
65
|
raise ConnectionError("mcpsmith Chrome is not running")
|
|
66
66
|
return await browser.cookies_for(host, st["port"])
|
|
67
67
|
|
|
68
|
-
extra = {
|
|
68
|
+
extra = {
|
|
69
|
+
"cookie_provider": cookie_provider or from_chrome,
|
|
70
|
+
"login_path": plan.auth.login_path,
|
|
71
|
+
"session_cookie": plan.auth.session_cookie,
|
|
72
|
+
}
|
|
69
73
|
return Api(**api_kwargs(plan, source), secret=secret, proxy=proxy, **extra)
|
|
70
74
|
|
|
71
75
|
|
|
@@ -225,6 +225,16 @@ def test_browser_auth_warns():
|
|
|
225
225
|
assert any(i.level == "warning" and "token" in i.message for i in issues)
|
|
226
226
|
|
|
227
227
|
|
|
228
|
+
def test_session_cookie_validation():
|
|
229
|
+
plan = valid_plan()
|
|
230
|
+
plan.auth = AuthScheme(name="b", type="browser", login_path="/Login")
|
|
231
|
+
assert any(i.level == "warning" and "session_cookie" in i.message for i in validate(plan))
|
|
232
|
+
plan.auth.session_cookie = "sid"
|
|
233
|
+
assert not [i for i in validate(plan) if "session_cookie" in i.message]
|
|
234
|
+
plan.auth = AuthScheme(name="t", type="bearer", env="EX_MCP_TOKEN", session_cookie="sid")
|
|
235
|
+
assert any("only applies to browser" in e.message for e in _errors(plan))
|
|
236
|
+
|
|
237
|
+
|
|
228
238
|
def test_plan_schema_and_new_fields():
|
|
229
239
|
assert "transform" in Plan.model_json_schema()["$defs"]["ToolArg"]["properties"]
|
|
230
240
|
plan = valid_plan()
|
|
@@ -309,6 +309,27 @@ async def test_browser_auth_session_expired(status, loc):
|
|
|
309
309
|
assert "TOPSECRET" not in str(e.value)
|
|
310
310
|
|
|
311
311
|
|
|
312
|
+
@pytest.mark.parametrize("jar,sent", [({"tok": "CSRF-SECRET"}, 0), ({"tok": "CSRF-SECRET", "login": "x"}, 1)])
|
|
313
|
+
async def test_browser_auth_session_cookie(jar, sent):
|
|
314
|
+
seen = []
|
|
315
|
+
|
|
316
|
+
async def provider(host):
|
|
317
|
+
return jar
|
|
318
|
+
|
|
319
|
+
def handler(request):
|
|
320
|
+
seen.append(request)
|
|
321
|
+
return httpx.Response(200, json={})
|
|
322
|
+
|
|
323
|
+
api = make_api(handler, auth={"type": "browser"}, cookie_provider=provider, session_cookie="login")
|
|
324
|
+
if sent:
|
|
325
|
+
await api.call("GET", "/x")
|
|
326
|
+
else:
|
|
327
|
+
with pytest.raises(ApiError, match="says the session has expired") as e:
|
|
328
|
+
await api.call("GET", "/x")
|
|
329
|
+
assert "CSRF-SECRET" not in str(e.value)
|
|
330
|
+
assert len(seen) == sent
|
|
331
|
+
|
|
332
|
+
|
|
312
333
|
async def test_browser_auth_chrome_not_running():
|
|
313
334
|
async def down(host):
|
|
314
335
|
raise ConnectionError("no chrome")
|
|
@@ -336,11 +357,11 @@ async def test_make_api_browser_needs_running_chrome(monkeypatch):
|
|
|
336
357
|
prefix="x",
|
|
337
358
|
base_url="https://api.example.com",
|
|
338
359
|
env_prefix="EX_MCP",
|
|
339
|
-
auth=AuthScheme(name="b", type="browser", login_path="/Login"),
|
|
360
|
+
auth=AuthScheme(name="b", type="browser", login_path="/Login", session_cookie="sid"),
|
|
340
361
|
)
|
|
341
362
|
api = build(plan, None)
|
|
342
363
|
api.set_transport(httpx.MockTransport(lambda r: httpx.Response(200, json={})))
|
|
343
|
-
assert api.login_path == "/Login"
|
|
364
|
+
assert api.login_path == "/Login" and api.session_cookie == "sid"
|
|
344
365
|
with pytest.raises(ApiError, match="not running"):
|
|
345
366
|
await api.call("GET", "/x")
|
|
346
367
|
|
|
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
|
{mcpsmith-0.2.1 → mcpsmith-0.2.2}/docs/superpowers/specs/2026-10-20-mcp-everything-design.md
RENAMED
|
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
|