conduct-cli 0.9.7__tar.gz → 0.9.9__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.
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/PKG-INFO +1 -1
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/pyproject.toml +1 -1
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/main.py +49 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_switch.py +182 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/README.md +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/setup.cfg +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/setup.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/guard.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/guard_policy.md +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/__init__.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/base.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/posttooluse.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/precompact.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/pretooluse.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/session_parser.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/session_report_push.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/session_start.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/hooks/stop.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/log_util.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/memory.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/paxel.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli/tool_groups.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_advisory_and_hook.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_api_client.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_bash_operator_signature.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_bash_scan_target.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_blast_radius.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_cli_matrix.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_cmd_run.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_command_word_matcher.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_conductguard_mcp_stdio.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_drain_daemon_health.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_guard_approvals.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_hook_dispatch.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_instruction_files.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_journal_drain.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_log_util.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_proxy_coverage.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_proxy_env.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_report_tool_coverage.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_signed_policy.py +0 -0
- {conduct_cli-0.9.7 → conduct_cli-0.9.9}/tests/test_token_lifecycle.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "conduct-cli"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.9"
|
|
8
8
|
description = "CLI for Conduct AI — secure, govern, install agents, manage projects, run tests"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -552,6 +552,10 @@ def cmd_login(args):
|
|
|
552
552
|
api_url = (getattr(args, "server", None) or _load_config().get("api_url", _DEFAULT_API_URL)).rstrip("/")
|
|
553
553
|
web_url = _DEFAULT_WEB_URL
|
|
554
554
|
|
|
555
|
+
# Capture the workspace the user was on BEFORE login — so we can restore
|
|
556
|
+
# it after the login flow (which always returns the account's default).
|
|
557
|
+
prior_ws = _load_config().get("workspace_id") or _load_config().get("workspace") or ""
|
|
558
|
+
|
|
555
559
|
# --token escape hatch: paste agent_token directly (no browser)
|
|
556
560
|
manual_token = getattr(args, "token", None)
|
|
557
561
|
if manual_token:
|
|
@@ -576,6 +580,28 @@ def cmd_login(args):
|
|
|
576
580
|
cfg.pop("api_key", None)
|
|
577
581
|
_atomic_write(CONFIG_PATH, cfg)
|
|
578
582
|
|
|
583
|
+
# Restore prior workspace if login returned a different default. Otherwise
|
|
584
|
+
# every `conduct login` silently blows away the last `conduct switch`.
|
|
585
|
+
if prior_ws and str(prior_ws) != str(result["workspace_id"]):
|
|
586
|
+
try:
|
|
587
|
+
_hdrs_restore = {"Authorization": f"Bearer {result['agent_token']}", "Content-Type": "application/json"}
|
|
588
|
+
_r = api.req("POST", f"{api_url}/auth/switch-workspace", _hdrs_restore, {"workspace_id": prior_ws})
|
|
589
|
+
if _r and _r.get("agent_token"):
|
|
590
|
+
cfg["agent_token"] = _r["agent_token"]
|
|
591
|
+
if _r.get("refresh_token"):
|
|
592
|
+
cfg["refresh_token"] = _r["refresh_token"]
|
|
593
|
+
cfg["workspace"] = prior_ws
|
|
594
|
+
cfg["workspace_id"] = prior_ws
|
|
595
|
+
cfg["token_expires_at"] = (_dt.datetime.now(_dt.timezone.utc) + _dt.timedelta(seconds=int(_r.get("expires_in", 28800)))).isoformat()
|
|
596
|
+
_atomic_write(CONFIG_PATH, cfg)
|
|
597
|
+
# Update `result` so subsequent code (name display, sync) targets prior workspace
|
|
598
|
+
result["workspace_id"] = prior_ws
|
|
599
|
+
result["agent_token"] = cfg["agent_token"]
|
|
600
|
+
except SystemExit:
|
|
601
|
+
pass # not a member of prior workspace anymore, or endpoint missing — keep server default
|
|
602
|
+
except Exception:
|
|
603
|
+
pass
|
|
604
|
+
|
|
579
605
|
# Fetch workspaces so we can display the current one by name and nudge
|
|
580
606
|
# the user if their account has more than one (cross-surface switch drift).
|
|
581
607
|
try:
|
|
@@ -1438,10 +1464,33 @@ def cmd_switch(args):
|
|
|
1438
1464
|
new_id = str(chosen["id"])
|
|
1439
1465
|
new_name = chosen["name"]
|
|
1440
1466
|
|
|
1467
|
+
# Re-mint agent token bound to the new workspace so server-side attribution
|
|
1468
|
+
# follows the switch. Without this, POSTs (discover, audit, heartbeats)
|
|
1469
|
+
# keep hitting the workspace the token was originally minted for.
|
|
1470
|
+
try:
|
|
1471
|
+
_r = api.req("POST", f"{server}/auth/switch-workspace", hdrs, {"workspace_id": new_id})
|
|
1472
|
+
if _r and _r.get("agent_token"):
|
|
1473
|
+
cfg["agent_token"] = _r["agent_token"]
|
|
1474
|
+
if _r.get("refresh_token"):
|
|
1475
|
+
cfg["refresh_token"] = _r["refresh_token"]
|
|
1476
|
+
import datetime as _dt
|
|
1477
|
+
cfg["token_expires_at"] = (_dt.datetime.now(_dt.timezone.utc) + _dt.timedelta(seconds=int(_r.get("expires_in", 28800)))).isoformat()
|
|
1478
|
+
except SystemExit:
|
|
1479
|
+
print(f" {YELLOW}⚠ Could not re-mint token for new workspace — falling back to config-only switch{RESET}")
|
|
1480
|
+
except Exception as e:
|
|
1481
|
+
print(f" {YELLOW}⚠ Token re-mint failed ({e}) — falling back to config-only switch{RESET}")
|
|
1482
|
+
|
|
1441
1483
|
# Update ~/.conduct/config.json atomically
|
|
1442
1484
|
cfg["workspace"] = new_id; cfg["workspace_id"] = new_id
|
|
1443
1485
|
_atomic_write(CONFIG_PATH, cfg)
|
|
1444
1486
|
|
|
1487
|
+
# Fire coverage POST so the new workspace's "developer connected" widget
|
|
1488
|
+
# populates immediately. Without this the switch works but the widget
|
|
1489
|
+
# stays empty until the next `conduct login`.
|
|
1490
|
+
try:
|
|
1491
|
+
_report_tool_coverage()
|
|
1492
|
+
except Exception:
|
|
1493
|
+
pass
|
|
1445
1494
|
|
|
1446
1495
|
# Re-sync Guard policies for the new workspace
|
|
1447
1496
|
try:
|
|
@@ -288,3 +288,185 @@ def test_login_omits_nudge_when_single_workspace(tmp_path, capsys):
|
|
|
288
288
|
out = capsys.readouterr().out
|
|
289
289
|
assert "Only" in out
|
|
290
290
|
assert "conduct switch" not in out
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# ---------------------------------------------------------------------------
|
|
294
|
+
# Fix A regression: cmd_switch must re-mint the agent token by calling
|
|
295
|
+
# POST /auth/switch-workspace so server-side attribution follows the local
|
|
296
|
+
# workspace flip. Without this, POSTs after switch keep hitting the
|
|
297
|
+
# original workspace.
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
def test_switch_remints_agent_token_via_endpoint(tmp_path, monkeypatch, capsys):
|
|
301
|
+
from conduct_cli import main as m
|
|
302
|
+
from conduct_cli import guard as g
|
|
303
|
+
|
|
304
|
+
cfg_path = tmp_path / "conduct" / "config.json"
|
|
305
|
+
cfg_path.parent.mkdir(parents=True)
|
|
306
|
+
cfg_path.write_text(json.dumps({
|
|
307
|
+
"api_url": "https://api.conductai.ai",
|
|
308
|
+
"agent_token": "cond_agt_ORIGINAL_engineering",
|
|
309
|
+
"workspace": "ef0a7e36-0000-0000-0000-000000000001",
|
|
310
|
+
}))
|
|
311
|
+
|
|
312
|
+
args = _make_args(workspace="Marketing")
|
|
313
|
+
remint_response = {
|
|
314
|
+
"agent_token": "cond_agt_NEW_marketing_scoped",
|
|
315
|
+
"refresh_token": "cond_ref_new_refresh",
|
|
316
|
+
"expires_in": 28800,
|
|
317
|
+
"workspace_id": "ab1b2c3d-0000-0000-0000-000000000002",
|
|
318
|
+
"user_id": "user_test",
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
def _api_req(method, url, hdrs, body=None, timeout=30):
|
|
322
|
+
if method == "GET" and url.endswith("/projects"):
|
|
323
|
+
return _fake_workspaces()
|
|
324
|
+
if method == "POST" and url.endswith("/auth/switch-workspace"):
|
|
325
|
+
assert body == {"workspace_id": "ab1b2c3d-0000-0000-0000-000000000002"}
|
|
326
|
+
return remint_response
|
|
327
|
+
raise AssertionError(f"unexpected call {method} {url}")
|
|
328
|
+
|
|
329
|
+
with (
|
|
330
|
+
patch.object(m, "CONFIG_PATH", cfg_path),
|
|
331
|
+
patch("pathlib.Path.home", return_value=tmp_path),
|
|
332
|
+
patch.object(m.api, "req", side_effect=_api_req),
|
|
333
|
+
patch.object(g, "_req", return_value={"version": "1", "rules": []}),
|
|
334
|
+
patch.object(g, "_save_policy"),
|
|
335
|
+
):
|
|
336
|
+
m.cmd_switch(args)
|
|
337
|
+
|
|
338
|
+
updated_cfg = json.loads(cfg_path.read_text())
|
|
339
|
+
assert updated_cfg["agent_token"] == "cond_agt_NEW_marketing_scoped"
|
|
340
|
+
assert updated_cfg["refresh_token"] == "cond_ref_new_refresh"
|
|
341
|
+
assert updated_cfg["workspace_id"] == "ab1b2c3d-0000-0000-0000-000000000002"
|
|
342
|
+
assert "token_expires_at" in updated_cfg
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_switch_falls_back_gracefully_when_remint_fails(tmp_path, monkeypatch, capsys):
|
|
346
|
+
"""If the switch-workspace endpoint 401s (older server), config-only switch still applies."""
|
|
347
|
+
from conduct_cli import main as m
|
|
348
|
+
from conduct_cli import guard as g
|
|
349
|
+
|
|
350
|
+
cfg_path = tmp_path / "conduct" / "config.json"
|
|
351
|
+
cfg_path.parent.mkdir(parents=True)
|
|
352
|
+
cfg_path.write_text(json.dumps({
|
|
353
|
+
"api_url": "https://api.conductai.ai",
|
|
354
|
+
"agent_token": "cond_agt_ORIGINAL",
|
|
355
|
+
"workspace": "ef0a7e36-0000-0000-0000-000000000001",
|
|
356
|
+
}))
|
|
357
|
+
args = _make_args(workspace="Marketing")
|
|
358
|
+
|
|
359
|
+
def _api_req(method, url, hdrs, body=None, timeout=30):
|
|
360
|
+
if method == "GET" and url.endswith("/projects"):
|
|
361
|
+
return _fake_workspaces()
|
|
362
|
+
# Endpoint failure — mimic api.req's SystemExit on HTTP error
|
|
363
|
+
raise SystemExit(1)
|
|
364
|
+
|
|
365
|
+
with (
|
|
366
|
+
patch.object(m, "CONFIG_PATH", cfg_path),
|
|
367
|
+
patch("pathlib.Path.home", return_value=tmp_path),
|
|
368
|
+
patch.object(m.api, "req", side_effect=_api_req),
|
|
369
|
+
patch.object(g, "_req", return_value={"version": "1", "rules": []}),
|
|
370
|
+
patch.object(g, "_save_policy"),
|
|
371
|
+
):
|
|
372
|
+
m.cmd_switch(args)
|
|
373
|
+
|
|
374
|
+
updated_cfg = json.loads(cfg_path.read_text())
|
|
375
|
+
# Token unchanged (fallback preserves the original) but workspace_id updated
|
|
376
|
+
assert updated_cfg["agent_token"] == "cond_agt_ORIGINAL"
|
|
377
|
+
assert updated_cfg["workspace_id"] == "ab1b2c3d-0000-0000-0000-000000000002"
|
|
378
|
+
out = capsys.readouterr().out
|
|
379
|
+
assert "falling back to config-only switch" in out
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# ---------------------------------------------------------------------------
|
|
383
|
+
# 0.9.9 — cmd_switch must fire _report_tool_coverage so the new workspace's
|
|
384
|
+
# "developer connected" widget populates immediately (not just after next login)
|
|
385
|
+
# ---------------------------------------------------------------------------
|
|
386
|
+
|
|
387
|
+
def test_switch_fires_tool_coverage(tmp_path, monkeypatch, capsys):
|
|
388
|
+
from conduct_cli import main as m
|
|
389
|
+
from conduct_cli import guard as g
|
|
390
|
+
|
|
391
|
+
cfg_path = tmp_path / "conduct" / "config.json"
|
|
392
|
+
cfg_path.parent.mkdir(parents=True)
|
|
393
|
+
cfg_path.write_text(json.dumps({
|
|
394
|
+
"api_url": "https://api.conductai.ai",
|
|
395
|
+
"agent_token": "cond_agt_test",
|
|
396
|
+
"workspace": "ef0a7e36-0000-0000-0000-000000000001",
|
|
397
|
+
}))
|
|
398
|
+
args = _make_args(workspace="Marketing")
|
|
399
|
+
|
|
400
|
+
def _api_req(method, url, hdrs, body=None, timeout=30):
|
|
401
|
+
if method == "GET" and url.endswith("/projects"):
|
|
402
|
+
return _fake_workspaces()
|
|
403
|
+
if method == "POST" and url.endswith("/auth/switch-workspace"):
|
|
404
|
+
return {"agent_token": "cond_agt_new", "expires_in": 28800, "workspace_id": body["workspace_id"], "user_id": "u1"}
|
|
405
|
+
raise AssertionError(f"unexpected call {method} {url}")
|
|
406
|
+
|
|
407
|
+
with (
|
|
408
|
+
patch.object(m, "CONFIG_PATH", cfg_path),
|
|
409
|
+
patch("pathlib.Path.home", return_value=tmp_path),
|
|
410
|
+
patch.object(m.api, "req", side_effect=_api_req),
|
|
411
|
+
patch.object(g, "_req", return_value={"version": "1", "rules": []}),
|
|
412
|
+
patch.object(g, "_save_policy"),
|
|
413
|
+
patch.object(m, "_report_tool_coverage") as coverage,
|
|
414
|
+
):
|
|
415
|
+
m.cmd_switch(args)
|
|
416
|
+
|
|
417
|
+
coverage.assert_called_once()
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# ---------------------------------------------------------------------------
|
|
421
|
+
# 0.9.9 — cmd_login must preserve the prior workspace_id if it differs from
|
|
422
|
+
# the server-returned default. Otherwise every re-login blows away the user's
|
|
423
|
+
# last `conduct switch`.
|
|
424
|
+
# ---------------------------------------------------------------------------
|
|
425
|
+
|
|
426
|
+
def test_login_preserves_prior_workspace(tmp_path, capsys):
|
|
427
|
+
from conduct_cli import main as m
|
|
428
|
+
|
|
429
|
+
cfg_path = tmp_path / "config.json"
|
|
430
|
+
# Prior state: user was on Marketing (via a previous switch)
|
|
431
|
+
cfg_path.write_text(json.dumps({
|
|
432
|
+
"api_url": "https://api.conductai.ai",
|
|
433
|
+
"agent_token": "cond_agt_old",
|
|
434
|
+
"workspace": "ab1b2c3d-0000-0000-0000-000000000002",
|
|
435
|
+
"workspace_id": "ab1b2c3d-0000-0000-0000-000000000002",
|
|
436
|
+
}))
|
|
437
|
+
args = _make_args(server=None, token=None)
|
|
438
|
+
|
|
439
|
+
# Login returns Engineering (server default) — but prior was Marketing
|
|
440
|
+
fake_login_result = {
|
|
441
|
+
"agent_token": "cond_agt_from_login_engineering",
|
|
442
|
+
"workspace_id": "ef0a7e36-0000-0000-0000-000000000001",
|
|
443
|
+
}
|
|
444
|
+
remint_response = {
|
|
445
|
+
"agent_token": "cond_agt_reminted_for_marketing",
|
|
446
|
+
"refresh_token": "cond_ref_reminted",
|
|
447
|
+
"expires_in": 28800,
|
|
448
|
+
"workspace_id": "ab1b2c3d-0000-0000-0000-000000000002",
|
|
449
|
+
"user_id": "u1",
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
def _api_req(method, url, hdrs, body=None, timeout=30):
|
|
453
|
+
if method == "POST" and url.endswith("/auth/switch-workspace"):
|
|
454
|
+
# Should be called with prior_ws (Marketing UUID)
|
|
455
|
+
assert body == {"workspace_id": "ab1b2c3d-0000-0000-0000-000000000002"}
|
|
456
|
+
return remint_response
|
|
457
|
+
if method == "GET" and url.endswith("/projects"):
|
|
458
|
+
return _fake_workspaces()
|
|
459
|
+
raise AssertionError(f"unexpected call {method} {url}")
|
|
460
|
+
|
|
461
|
+
with (
|
|
462
|
+
patch.object(m, "CONFIG_PATH", cfg_path),
|
|
463
|
+
patch.object(m, "_web_login_flow", return_value=fake_login_result),
|
|
464
|
+
patch.object(m.api, "req", side_effect=_api_req),
|
|
465
|
+
patch("conduct_cli.guard.cmd_guard_sync", return_value=None),
|
|
466
|
+
):
|
|
467
|
+
m.cmd_login(args)
|
|
468
|
+
|
|
469
|
+
updated_cfg = json.loads(cfg_path.read_text())
|
|
470
|
+
assert updated_cfg["workspace_id"] == "ab1b2c3d-0000-0000-0000-000000000002"
|
|
471
|
+
assert updated_cfg["agent_token"] == "cond_agt_reminted_for_marketing"
|
|
472
|
+
assert updated_cfg["refresh_token"] == "cond_ref_reminted"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|