mcpmon 0.1.4 ā 0.3.0
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.
- package/.github/workflows/release.yml +1 -1
- package/.nojekyll +0 -0
- package/.pre-commit-config.yaml +44 -0
- package/README.md +59 -4
- package/__main__.py +6 -0
- package/index.html +414 -0
- package/mcpmon.py +244 -36
- package/mcpmon.test.ts +441 -0
- package/mcpmon.ts +202 -35
- package/package.json +1 -1
- package/pyproject.toml +11 -1
- package/tests/__init__.py +1 -0
- package/tests/test_mcpmon.py +493 -0
- package/.github/.tmp/.generated-actions/run-pypi-publish-in-docker-container/action.yml +0 -1
- package/dist/mcpmon-0.1.4-py3-none-any.whl +0 -0
- package/dist/mcpmon-0.1.4-py3-none-any.whl.publish.attestation +0 -1
- package/dist/mcpmon-0.1.4.tar.gz +0 -0
- package/dist/mcpmon-0.1.4.tar.gz.publish.attestation +0 -1
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
"""Unit and integration tests for mcpmon."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import signal
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from unittest.mock import MagicMock, patch
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
# Add parent directory to path for imports
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
18
|
+
|
|
19
|
+
from mcpmon import (
|
|
20
|
+
Logger,
|
|
21
|
+
LogLevel,
|
|
22
|
+
get_change_type_name,
|
|
23
|
+
should_reload,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# =============================================================================
|
|
28
|
+
# Unit Tests - Logger
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestLogLevel:
|
|
33
|
+
"""Test LogLevel enum."""
|
|
34
|
+
|
|
35
|
+
def test_level_ordering(self):
|
|
36
|
+
"""Log levels should be ordered: QUIET < NORMAL < VERBOSE < DEBUG."""
|
|
37
|
+
assert LogLevel.QUIET < LogLevel.NORMAL
|
|
38
|
+
assert LogLevel.NORMAL < LogLevel.VERBOSE
|
|
39
|
+
assert LogLevel.VERBOSE < LogLevel.DEBUG
|
|
40
|
+
|
|
41
|
+
def test_level_values(self):
|
|
42
|
+
"""Log levels should have expected numeric values."""
|
|
43
|
+
assert LogLevel.QUIET == 0
|
|
44
|
+
assert LogLevel.NORMAL == 1
|
|
45
|
+
assert LogLevel.VERBOSE == 2
|
|
46
|
+
assert LogLevel.DEBUG == 3
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestLogger:
|
|
50
|
+
"""Test Logger class."""
|
|
51
|
+
|
|
52
|
+
def test_default_settings(self):
|
|
53
|
+
"""Logger should have sensible defaults."""
|
|
54
|
+
logger = Logger()
|
|
55
|
+
assert logger.level == LogLevel.NORMAL
|
|
56
|
+
assert logger.show_timestamps is False
|
|
57
|
+
assert logger.log_file is None
|
|
58
|
+
|
|
59
|
+
def test_format_basic(self):
|
|
60
|
+
"""Format should include [mcpmon] prefix."""
|
|
61
|
+
logger = Logger()
|
|
62
|
+
msg = logger._format("test message")
|
|
63
|
+
assert msg.startswith("[mcpmon]")
|
|
64
|
+
assert "test message" in msg
|
|
65
|
+
|
|
66
|
+
def test_format_with_pid(self):
|
|
67
|
+
"""Format should include PID when provided."""
|
|
68
|
+
logger = Logger()
|
|
69
|
+
msg = logger._format("test", pid=12345)
|
|
70
|
+
assert "pid:12345" in msg
|
|
71
|
+
|
|
72
|
+
def test_format_with_timestamps(self):
|
|
73
|
+
"""Format should include timestamp when enabled."""
|
|
74
|
+
logger = Logger(show_timestamps=True)
|
|
75
|
+
msg = logger._format("test")
|
|
76
|
+
# Timestamp format is HH:MM:SS
|
|
77
|
+
import re
|
|
78
|
+
assert re.search(r"\d{2}:\d{2}:\d{2}", msg)
|
|
79
|
+
|
|
80
|
+
def test_format_without_timestamps(self):
|
|
81
|
+
"""Format should not include timestamp when disabled."""
|
|
82
|
+
logger = Logger(show_timestamps=False)
|
|
83
|
+
msg = logger._format("test")
|
|
84
|
+
import re
|
|
85
|
+
# Should not have time pattern in basic format
|
|
86
|
+
assert not re.search(r"\d{2}:\d{2}:\d{2}", msg)
|
|
87
|
+
|
|
88
|
+
def test_info_respects_level(self, capsys):
|
|
89
|
+
"""info() should respect log level."""
|
|
90
|
+
logger = Logger(level=LogLevel.QUIET)
|
|
91
|
+
logger.info("should not appear")
|
|
92
|
+
captured = capsys.readouterr()
|
|
93
|
+
assert "should not appear" not in captured.err
|
|
94
|
+
|
|
95
|
+
logger = Logger(level=LogLevel.NORMAL)
|
|
96
|
+
logger.info("should appear")
|
|
97
|
+
captured = capsys.readouterr()
|
|
98
|
+
assert "should appear" in captured.err
|
|
99
|
+
|
|
100
|
+
def test_verbose_respects_level(self, capsys):
|
|
101
|
+
"""verbose() should respect log level."""
|
|
102
|
+
logger = Logger(level=LogLevel.NORMAL)
|
|
103
|
+
logger.verbose("should not appear")
|
|
104
|
+
captured = capsys.readouterr()
|
|
105
|
+
assert "should not appear" not in captured.err
|
|
106
|
+
|
|
107
|
+
logger = Logger(level=LogLevel.VERBOSE)
|
|
108
|
+
logger.verbose("should appear")
|
|
109
|
+
captured = capsys.readouterr()
|
|
110
|
+
assert "should appear" in captured.err
|
|
111
|
+
|
|
112
|
+
def test_debug_respects_level(self, capsys):
|
|
113
|
+
"""debug() should respect log level."""
|
|
114
|
+
logger = Logger(level=LogLevel.VERBOSE)
|
|
115
|
+
logger.debug("should not appear")
|
|
116
|
+
captured = capsys.readouterr()
|
|
117
|
+
assert "should not appear" not in captured.err
|
|
118
|
+
|
|
119
|
+
logger = Logger(level=LogLevel.DEBUG)
|
|
120
|
+
logger.debug("should appear")
|
|
121
|
+
captured = capsys.readouterr()
|
|
122
|
+
assert "DEBUG:" in captured.err
|
|
123
|
+
assert "should appear" in captured.err
|
|
124
|
+
|
|
125
|
+
def test_error_always_shown(self, capsys):
|
|
126
|
+
"""error() should always be shown, even at QUIET level."""
|
|
127
|
+
logger = Logger(level=LogLevel.QUIET)
|
|
128
|
+
logger.error("error message")
|
|
129
|
+
captured = capsys.readouterr()
|
|
130
|
+
assert "ERROR:" in captured.err
|
|
131
|
+
assert "error message" in captured.err
|
|
132
|
+
|
|
133
|
+
def test_log_file_write(self):
|
|
134
|
+
"""Logger should write to file when configured."""
|
|
135
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
|
|
136
|
+
log_path = Path(f.name)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
logger = Logger(log_file=log_path)
|
|
140
|
+
logger.open_file()
|
|
141
|
+
logger.info("file test message")
|
|
142
|
+
logger.close_file()
|
|
143
|
+
|
|
144
|
+
content = log_path.read_text()
|
|
145
|
+
assert "file test message" in content
|
|
146
|
+
# File logs should always have timestamps
|
|
147
|
+
import re
|
|
148
|
+
assert re.search(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}", content)
|
|
149
|
+
finally:
|
|
150
|
+
log_path.unlink(missing_ok=True)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# =============================================================================
|
|
154
|
+
# Unit Tests - File Watching
|
|
155
|
+
# =============================================================================
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestShouldReload:
|
|
159
|
+
"""Test should_reload function."""
|
|
160
|
+
|
|
161
|
+
def test_matching_extension(self):
|
|
162
|
+
"""Should return True for matching extensions."""
|
|
163
|
+
from watchfiles import Change
|
|
164
|
+
|
|
165
|
+
changes = {(Change.modified, "/path/to/file.py")}
|
|
166
|
+
extensions = {"py"}
|
|
167
|
+
|
|
168
|
+
reload, matches = should_reload(changes, extensions)
|
|
169
|
+
assert reload is True
|
|
170
|
+
assert len(matches) == 1
|
|
171
|
+
assert matches[0][0] == "modified"
|
|
172
|
+
assert matches[0][1] == "/path/to/file.py"
|
|
173
|
+
|
|
174
|
+
def test_non_matching_extension(self):
|
|
175
|
+
"""Should return False for non-matching extensions."""
|
|
176
|
+
from watchfiles import Change
|
|
177
|
+
|
|
178
|
+
changes = {(Change.modified, "/path/to/file.txt")}
|
|
179
|
+
extensions = {"py"}
|
|
180
|
+
|
|
181
|
+
reload, matches = should_reload(changes, extensions)
|
|
182
|
+
assert reload is False
|
|
183
|
+
assert len(matches) == 0
|
|
184
|
+
|
|
185
|
+
def test_multiple_extensions(self):
|
|
186
|
+
"""Should match multiple configured extensions."""
|
|
187
|
+
from watchfiles import Change
|
|
188
|
+
|
|
189
|
+
extensions = {"py", "json", "yaml"}
|
|
190
|
+
|
|
191
|
+
for ext in ["py", "json", "yaml"]:
|
|
192
|
+
changes = {(Change.modified, f"/path/to/file.{ext}")}
|
|
193
|
+
reload, _ = should_reload(changes, extensions)
|
|
194
|
+
assert reload is True
|
|
195
|
+
|
|
196
|
+
def test_deleted_files_ignored(self):
|
|
197
|
+
"""Should not reload for deleted files."""
|
|
198
|
+
from watchfiles import Change
|
|
199
|
+
|
|
200
|
+
changes = {(Change.deleted, "/path/to/file.py")}
|
|
201
|
+
extensions = {"py"}
|
|
202
|
+
|
|
203
|
+
reload, matches = should_reload(changes, extensions)
|
|
204
|
+
assert reload is False
|
|
205
|
+
|
|
206
|
+
def test_added_files_trigger_reload(self):
|
|
207
|
+
"""Should reload for newly added files."""
|
|
208
|
+
from watchfiles import Change
|
|
209
|
+
|
|
210
|
+
changes = {(Change.added, "/path/to/new_file.py")}
|
|
211
|
+
extensions = {"py"}
|
|
212
|
+
|
|
213
|
+
reload, matches = should_reload(changes, extensions)
|
|
214
|
+
assert reload is True
|
|
215
|
+
assert matches[0][0] == "added"
|
|
216
|
+
|
|
217
|
+
def test_multiple_changes(self):
|
|
218
|
+
"""Should handle multiple file changes."""
|
|
219
|
+
from watchfiles import Change
|
|
220
|
+
|
|
221
|
+
changes = {
|
|
222
|
+
(Change.modified, "/path/to/file1.py"),
|
|
223
|
+
(Change.modified, "/path/to/file2.py"),
|
|
224
|
+
(Change.modified, "/path/to/file3.txt"), # Should not match
|
|
225
|
+
}
|
|
226
|
+
extensions = {"py"}
|
|
227
|
+
|
|
228
|
+
reload, matches = should_reload(changes, extensions)
|
|
229
|
+
assert reload is True
|
|
230
|
+
assert len(matches) == 2
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class TestGetChangeTypeName:
|
|
234
|
+
"""Test get_change_type_name function."""
|
|
235
|
+
|
|
236
|
+
def test_change_types(self):
|
|
237
|
+
"""Should return human-readable change type names."""
|
|
238
|
+
from watchfiles import Change
|
|
239
|
+
|
|
240
|
+
assert get_change_type_name(Change.added) == "added"
|
|
241
|
+
assert get_change_type_name(Change.modified) == "modified"
|
|
242
|
+
assert get_change_type_name(Change.deleted) == "deleted"
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# =============================================================================
|
|
246
|
+
# Integration Tests
|
|
247
|
+
# =============================================================================
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class TestIntegration:
|
|
251
|
+
"""Integration tests for mcpmon."""
|
|
252
|
+
|
|
253
|
+
@pytest.fixture
|
|
254
|
+
def temp_dir(self):
|
|
255
|
+
"""Create a temporary directory for testing."""
|
|
256
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
257
|
+
yield Path(tmpdir)
|
|
258
|
+
|
|
259
|
+
@pytest.fixture
|
|
260
|
+
def dummy_server(self, temp_dir):
|
|
261
|
+
"""Create a dummy server script that logs when started."""
|
|
262
|
+
server_script = temp_dir / "server.py"
|
|
263
|
+
server_script.write_text("""
|
|
264
|
+
import sys
|
|
265
|
+
import time
|
|
266
|
+
import signal
|
|
267
|
+
|
|
268
|
+
print(f"[test-server] Started (pid={__import__('os').getpid()})", file=sys.stderr)
|
|
269
|
+
sys.stderr.flush()
|
|
270
|
+
|
|
271
|
+
def handle_term(sig, frame):
|
|
272
|
+
print("[test-server] Received SIGTERM", file=sys.stderr)
|
|
273
|
+
sys.exit(0)
|
|
274
|
+
|
|
275
|
+
signal.signal(signal.SIGTERM, handle_term)
|
|
276
|
+
|
|
277
|
+
while True:
|
|
278
|
+
time.sleep(0.1)
|
|
279
|
+
""")
|
|
280
|
+
return server_script
|
|
281
|
+
|
|
282
|
+
def test_mcpmon_starts_server(self, temp_dir, dummy_server):
|
|
283
|
+
"""mcpmon should start the server subprocess."""
|
|
284
|
+
proc = subprocess.Popen(
|
|
285
|
+
[
|
|
286
|
+
sys.executable,
|
|
287
|
+
"-m", "mcpmon",
|
|
288
|
+
"--watch", str(temp_dir),
|
|
289
|
+
"--", sys.executable, str(dummy_server),
|
|
290
|
+
],
|
|
291
|
+
stderr=subprocess.PIPE,
|
|
292
|
+
stdout=subprocess.PIPE,
|
|
293
|
+
cwd=Path(__file__).parent.parent,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
# Wait for startup
|
|
298
|
+
time.sleep(2)
|
|
299
|
+
|
|
300
|
+
# Check that mcpmon is running
|
|
301
|
+
assert proc.poll() is None, "mcpmon should still be running"
|
|
302
|
+
|
|
303
|
+
finally:
|
|
304
|
+
proc.terminate()
|
|
305
|
+
proc.wait(timeout=5)
|
|
306
|
+
|
|
307
|
+
def test_mcpmon_restarts_on_change(self, temp_dir, dummy_server):
|
|
308
|
+
"""mcpmon should restart server when watched file changes."""
|
|
309
|
+
# Create a watched file
|
|
310
|
+
watched_file = temp_dir / "watched.py"
|
|
311
|
+
watched_file.write_text("# initial")
|
|
312
|
+
|
|
313
|
+
log_file = temp_dir / "mcpmon.log"
|
|
314
|
+
|
|
315
|
+
proc = subprocess.Popen(
|
|
316
|
+
[
|
|
317
|
+
sys.executable,
|
|
318
|
+
"-m", "mcpmon",
|
|
319
|
+
"--watch", str(temp_dir),
|
|
320
|
+
"--ext", "py",
|
|
321
|
+
"--log-file", str(log_file),
|
|
322
|
+
"--", sys.executable, str(dummy_server),
|
|
323
|
+
],
|
|
324
|
+
stderr=subprocess.PIPE,
|
|
325
|
+
stdout=subprocess.PIPE,
|
|
326
|
+
cwd=Path(__file__).parent.parent,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
# Wait for initial startup
|
|
331
|
+
time.sleep(2)
|
|
332
|
+
|
|
333
|
+
# Trigger a reload by modifying the watched file
|
|
334
|
+
watched_file.write_text("# modified")
|
|
335
|
+
|
|
336
|
+
# Wait for restart
|
|
337
|
+
time.sleep(2)
|
|
338
|
+
|
|
339
|
+
# Check log file for restart message
|
|
340
|
+
log_content = log_file.read_text()
|
|
341
|
+
assert "Restart #1" in log_content, f"Expected restart in log: {log_content}"
|
|
342
|
+
|
|
343
|
+
finally:
|
|
344
|
+
proc.terminate()
|
|
345
|
+
proc.wait(timeout=5)
|
|
346
|
+
|
|
347
|
+
def test_mcpmon_ignores_non_matching_files(self, temp_dir, dummy_server):
|
|
348
|
+
"""mcpmon should ignore changes to non-matching extensions."""
|
|
349
|
+
# Create a non-matching file
|
|
350
|
+
other_file = temp_dir / "readme.txt"
|
|
351
|
+
other_file.write_text("initial")
|
|
352
|
+
|
|
353
|
+
log_file = temp_dir / "mcpmon.log"
|
|
354
|
+
|
|
355
|
+
proc = subprocess.Popen(
|
|
356
|
+
[
|
|
357
|
+
sys.executable,
|
|
358
|
+
"-m", "mcpmon",
|
|
359
|
+
"--watch", str(temp_dir),
|
|
360
|
+
"--ext", "py", # Only watch .py files
|
|
361
|
+
"--debug",
|
|
362
|
+
"--log-file", str(log_file),
|
|
363
|
+
"--", sys.executable, str(dummy_server),
|
|
364
|
+
],
|
|
365
|
+
stderr=subprocess.PIPE,
|
|
366
|
+
stdout=subprocess.PIPE,
|
|
367
|
+
cwd=Path(__file__).parent.parent,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
try:
|
|
371
|
+
# Wait for initial startup
|
|
372
|
+
time.sleep(2)
|
|
373
|
+
|
|
374
|
+
# Modify the non-matching file
|
|
375
|
+
other_file.write_text("modified")
|
|
376
|
+
|
|
377
|
+
# Wait a bit
|
|
378
|
+
time.sleep(2)
|
|
379
|
+
|
|
380
|
+
# Check that no restart occurred
|
|
381
|
+
log_content = log_file.read_text()
|
|
382
|
+
assert "Restart #1" not in log_content, "Should not restart for .txt file"
|
|
383
|
+
# But should log that it was ignored (debug mode)
|
|
384
|
+
assert "Ignored" in log_content or "readme.txt" in log_content
|
|
385
|
+
|
|
386
|
+
finally:
|
|
387
|
+
proc.terminate()
|
|
388
|
+
proc.wait(timeout=5)
|
|
389
|
+
|
|
390
|
+
def test_mcpmon_graceful_shutdown(self, temp_dir, dummy_server):
|
|
391
|
+
"""mcpmon should handle SIGTERM gracefully."""
|
|
392
|
+
log_file = temp_dir / "mcpmon.log"
|
|
393
|
+
|
|
394
|
+
proc = subprocess.Popen(
|
|
395
|
+
[
|
|
396
|
+
sys.executable,
|
|
397
|
+
"-m", "mcpmon",
|
|
398
|
+
"--watch", str(temp_dir),
|
|
399
|
+
"--log-file", str(log_file),
|
|
400
|
+
"--", sys.executable, str(dummy_server),
|
|
401
|
+
],
|
|
402
|
+
stderr=subprocess.PIPE,
|
|
403
|
+
stdout=subprocess.PIPE,
|
|
404
|
+
cwd=Path(__file__).parent.parent,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
# Wait for startup
|
|
409
|
+
time.sleep(2)
|
|
410
|
+
|
|
411
|
+
# Send SIGTERM
|
|
412
|
+
proc.terminate()
|
|
413
|
+
|
|
414
|
+
# Wait for graceful shutdown
|
|
415
|
+
proc.wait(timeout=5)
|
|
416
|
+
|
|
417
|
+
# Check log for shutdown message
|
|
418
|
+
log_content = log_file.read_text()
|
|
419
|
+
assert "Shutdown complete" in log_content
|
|
420
|
+
|
|
421
|
+
except subprocess.TimeoutExpired:
|
|
422
|
+
proc.kill()
|
|
423
|
+
pytest.fail("mcpmon did not shut down gracefully")
|
|
424
|
+
|
|
425
|
+
def test_mcpmon_timestamps_option(self, temp_dir, dummy_server):
|
|
426
|
+
"""--timestamps should add timestamps to output."""
|
|
427
|
+
log_file = temp_dir / "mcpmon.log"
|
|
428
|
+
|
|
429
|
+
proc = subprocess.Popen(
|
|
430
|
+
[
|
|
431
|
+
sys.executable,
|
|
432
|
+
"-m", "mcpmon",
|
|
433
|
+
"--watch", str(temp_dir),
|
|
434
|
+
"--timestamps",
|
|
435
|
+
"--log-file", str(log_file),
|
|
436
|
+
"--", sys.executable, str(dummy_server),
|
|
437
|
+
],
|
|
438
|
+
stderr=subprocess.PIPE,
|
|
439
|
+
stdout=subprocess.PIPE,
|
|
440
|
+
cwd=Path(__file__).parent.parent,
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
time.sleep(2)
|
|
445
|
+
finally:
|
|
446
|
+
proc.terminate()
|
|
447
|
+
proc.wait(timeout=5)
|
|
448
|
+
|
|
449
|
+
# Log file always has full timestamps
|
|
450
|
+
import re
|
|
451
|
+
log_content = log_file.read_text()
|
|
452
|
+
assert re.search(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}", log_content)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# =============================================================================
|
|
456
|
+
# CLI Argument Tests
|
|
457
|
+
# =============================================================================
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class TestCLI:
|
|
461
|
+
"""Test CLI argument parsing."""
|
|
462
|
+
|
|
463
|
+
def test_help_flag(self):
|
|
464
|
+
"""--help should show usage and exit 0."""
|
|
465
|
+
result = subprocess.run(
|
|
466
|
+
[sys.executable, "-m", "mcpmon", "--help"],
|
|
467
|
+
capture_output=True,
|
|
468
|
+
text=True,
|
|
469
|
+
cwd=Path(__file__).parent.parent,
|
|
470
|
+
)
|
|
471
|
+
assert result.returncode == 0
|
|
472
|
+
assert "Usage:" in result.stdout or "usage:" in result.stdout
|
|
473
|
+
|
|
474
|
+
def test_missing_command_error(self):
|
|
475
|
+
"""Should error if no command specified."""
|
|
476
|
+
result = subprocess.run(
|
|
477
|
+
[sys.executable, "-m", "mcpmon", "--watch", "."],
|
|
478
|
+
capture_output=True,
|
|
479
|
+
text=True,
|
|
480
|
+
cwd=Path(__file__).parent.parent,
|
|
481
|
+
)
|
|
482
|
+
assert result.returncode != 0
|
|
483
|
+
assert "command" in result.stderr.lower() or "No command" in result.stderr
|
|
484
|
+
|
|
485
|
+
def test_version_in_help(self):
|
|
486
|
+
"""Help should include examples."""
|
|
487
|
+
result = subprocess.run(
|
|
488
|
+
[sys.executable, "-m", "mcpmon", "--help"],
|
|
489
|
+
capture_output=True,
|
|
490
|
+
text=True,
|
|
491
|
+
cwd=Path(__file__).parent.parent,
|
|
492
|
+
)
|
|
493
|
+
assert "Examples:" in result.stdout
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"name": "š", "description": "Run Docker container to upload Python distribution packages to PyPI", "inputs": {"user": {"description": "PyPI user", "required": false}, "password": {"description": "Password for your PyPI user or an access token", "required": false}, "repository-url": {"description": "The repository URL to use", "required": false}, "packages-dir": {"description": "The target directory for distribution", "required": false}, "verify-metadata": {"description": "Check metadata before uploading", "required": false}, "skip-existing": {"description": "Do not fail if a Python package distribution exists in the target package index", "required": false}, "verbose": {"description": "Show verbose output.", "required": false}, "print-hash": {"description": "Show hash values of files to be uploaded", "required": false}, "attestations": {"description": "[EXPERIMENTAL] Enable experimental support for PEP 740 attestations. Only works with PyPI and TestPyPI via Trusted Publishing.", "required": false}}, "runs": {"using": "docker", "image": "docker://ghcr.io/pypa/gh-action-pypi-publish:release-v1"}}
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"verification_material":{"certificate":"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","transparency_entries":[{"logIndex":"854724717","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"dsse","version":"0.0.1"},"integratedTime":"1769383533","inclusionPromise":{"signedEntryTimestamp":"MEQCIB4BSUl8FcjWriaoAZjRJcZ4Q4mtBJvXFdJJY5CGG4vCAiALbVdmSh0jVhxqATm9v29EnF0RCGS7D1dmMLPUHyvf9Q=="},"inclusionProof":{"logIndex":"732820455","rootHash":"eE1ekh3Xgk1rETOEFfJQR6ealKWkswYFPwZffvD5HLQ=","treeSize":"732820460","hashes":["pdGeTdNp4KglQuIDJuS3kpxwHtz8O2HLBWpvv1XO7LE=","/35+EsqjYiVWORgZJrJ8YmNvqD3zAGMwN16X+V0H4hw=","5Lpzt0SDkMvXkzuXQkM4IczaD3YieQihqNFRiJ/0j4I=","b5uNlze6Z88KmJxo+FmTFgLDzgycYBj0oyfE6wA63eY=","74KmBe4Kjlo2YGKnnWSZDq70uawwQKc6lVoDBeIyJZ0=","MZE7wMO38x+IyNk2UeqawjOiRE8nzWZDKxJ4B96w+ro=","8QqJNOeX+oNUh9Jl2kca4qTMqiJPStt58YXQM9mqAHw=","DwKwRQf8bLfvAlZCumHHqyln+KUP87MajXC3gQiRsXk=","rF9MeFdsXSqGF8vqzgxBkGyzLstktTr84yuHfN9eI14=","avGfYpplrUBIQg4ezCF3xrJGXOH8Uh/sOtaaNxjyqmo=","sss5FthkuTnwa241eO0hxXXBCR3QeypHRP73o9RzO9g=","jfukQMAVfCqCrlYwRqqUd/hbEP4yotGKGxR4+c54pfA=","OllZ6+8Si5cuPekJ1g/CP+sXnQHy2JgxYEUmTqicIqc=","dBkbakOCkLCPfwfbc9RJj5FE13JWP1L+K/QnvWbBXjw=","8UxdDATbCIzp+y4X0U33q0Ofz5gDN3girMGDR3eDQes=","i0MObzT1TBsOoj2fSfX5tVy/0RDAzekFRhxMA9dNLRk=","tpLJI2Sg9ZYY8iWiNmofGyoEHdnSBSJwiMmlIBYhOfQ=","O8/imYCTZtbxIc+69c8pb8IaxPdmSK7UPXh/ip+hoLI=","6OLWbtKvxLyT2cr4F2qdHh0quyYiSl/P7LpDfVrK6R8=","WFmkMhmL2tOzDi6lp4zGgaCzwux2vGOM44v1vr1wuDs=","F9MSQ5SmoFr+hoADclpdFY52/TLfHDnNPYb9ZNYO5gI=","T4DqWD42hAtN+vX8jKCWqoC4meE4JekI9LxYGCcPy1M="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n732820460\neE1ekh3Xgk1rETOEFfJQR6ealKWkswYFPwZffvD5HLQ=\n\nā rekor.sigstore.dev wNI9ajBGAiEAu2Y7qOW6TowsdnlINmGgzDLDuJ3Aef6HUi8Z+Oia+rACIQDpLVUacCCAMP3/rV66VfEaIJptyZcu7YttNHVsT2kKqw==\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZmQ2MjQ2YzYxYmJlYzNiMjU5ZDQ4MjFkMWVlY2E0ZGFhMjgxZDE1ODMxMDEzY2VjMTljMWEwMzU5NzgxY2JlMSJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjBiNWJiZDVjNjBmYzIyZmFhNWE0MjU2OGQxODUwYWNlZjkxYWUwZGI2NGEzNDJlMGQzMWQ3YjBhZTQ5MDcwYWEifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRRFFGek5wMkJYYXZuRWpZOTVINm83WVJ1VlJKK1FvNGF4a3Jya3lwa1JJWndJZ1RWQ3o0L0UvNy9EV0sySEF3OXorcERObzczblZscjVsWmE3NldiekxPVE09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1Vkc1ZFTkRRbWgxWjBGM1NVSkJaMGxWVUc5a1lVcEZlRTFTUzNkRFVYVXZVbWgyY0dSMUwxWTJhblpSZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwWmQwMVVTVEZOYWsxNVRsUk5lVmRvWTA1TmFsbDNUVlJKTVUxcVRYcE9WRTE1VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVnFOVFpJTjNBMVRIbDZTR1paTHpaS2Ntc3JMemMzVlhSbVUxUXllbEkxUW1wUlVsY0tVbUZLVkd0NVlYZE9aVE4wVUcxT1ZYaGlaREo0U2xOc2EyMHhhMGxXUVhWVmRUSlBORnBpVDJ4SVVVUmhXbWhJTmpaUFEwSlViM2RuWjFVeVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVkVLM2hKQ21wek9GSm5ZbFE0S3pCa2JuWkthekVyVHpoc1ZITjNkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMWRuV1VSV1VqQlNRVkZJTDBKR1FYZFViMXBOWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBUREpKZUU0emIzWmlWMDUzWWxjNWRRcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFpoUjFab1draE5kbUpYUm5CaWFrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUlNVVkZXR1IyWTIxMGJXSkhPVE5ZTWxKd1l6TkNhR1JIVG05TlJGbEhRMmx6UndwQlVWRkNaemM0ZDBGUlRVVkxSMUV3VGxSSmVWcEhVWGhQVkdkM1RqSk5lVTFxUlRSUFZHUm9XVEphYVUxVWF6Uk5iVlV3VFVSWk0wNXRTVEZOUjFVeENrNUhWWGRHVVZsTFMzZFpRa0pCUjBSMmVrRkNRa0ZSU0ZWdFZuTmFWMFo2V2xSQldrSm5iM0pDWjBWRlFWbFBMMDFCUlVaQ1FYUnBUVlJrTmt3eU1Xb0tZMGN4ZG1KcVFXUkNaMjl5UW1kRlJVRlpUeTlOUVVWSFFrRTVlVnBYV25wTU1taHNXVmRTZWt3eU1XaGhWelIzVDNkWlMwdDNXVUpDUVVkRWRucEJRZ3BEUVZGMFJFTjBiMlJJVW5kamVtOTJURE5TZG1FeVZuVk1iVVpxWkVkc2RtSnVUWFZhTW13d1lVaFdhV1JZVG14amJVNTJZbTVTYkdKdVVYVlpNamwwQ2sxR2QwZERhWE5IUVZGUlFtYzNPSGRCVVd0RlZHZDRUV0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRFd3lTWGhPTTI5MllsZE9kMkpYT1hVS1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTmlNMlI2VEROS2JHSkhWbWhqTWxWMVpWY3hjMUZJU214YWJrMTJZVWRXYUZwSVRYWmlWMFp3WW1wQk5BcENaMjl5UW1kRlJVRlpUeTlOUVVWTFFrTnZUVXRIVVRCT1ZFbDVXa2RSZUU5VVozZE9NazE1VFdwRk5FOVVaR2haTWxwcFRWUnJORTF0VlRCTlJGa3pDazV0U1RGTlIxVXhUa2RWZDBoUldVdExkMWxDUWtGSFJIWjZRVUpEZDFGUVJFRXhibUZZVW05a1YwbDBZVWM1ZW1SSFZtdE5RelJIUTJselIwRlJVVUlLWnpjNGQwRlJkMFZKUVhkbFlVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKSmVFNHpiM1ppVjA1M1lsYzVkVTFFWjBkRGFYTkhRVkZSUWdwbk56aDNRVkV3UlV0bmQyOWFSRkV4VFdwS2ExcEVSVFZQUkVFeldYcEplVTFVWnpWT01rWnFXbTFKZUU5VVozbGFWRkYzVG1wak1sbHFWWGRhVkZVd0NscFVRV1pDWjI5eVFtZEZSVUZaVHk5TlFVVlBRa0pGVFVRelNteGFiazEyWVVkV2FGcElUWFppVjBad1ltcEJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQUtRa0YzVFVOcVJYaE9SRWwzVGxSVk0wOUVSWGRLZDFsTFMzZFpRa0pCUjBSMmVrRkNSVUZSV2tSQ1pHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhZ3BpTWpCMldXcEZNMlZxUVZoQ1oyOXlRbWRGUlVGWlR5OU5RVVZTUWtGclRVSjZUVEpOZWswd1QwUlJkMWhCV1V0TGQxbENRa0ZIUkhaNlFVSkZaMUpQQ2tSRmVHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaWmFrVXpaV2s1ZEZrelFuUmlNalIyVEcxa2NHUkhhREZaYVRrellqTktjbHB0ZUhZS1pETk5kbU50Vm5OYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRsMFdWZHNkVTFFWjBkRGFYTkhRVkZSUW1jM09IZEJVazFGUzJkM2J3cGFSRkV4VFdwS2ExcEVSVFZQUkVFeldYcEplVTFVWnpWT01rWnFXbTFKZUU5VVozbGFWRkYzVG1wak1sbHFWWGRhVkZVd1dsUkJhRUpuYjNKQ1owVkZDa0ZaVHk5TlFVVlZRa0pOVFVWWVpIWmpiWFJ0WWtjNU0xZ3lVbkJqTTBKb1pFZE9iMDFHU1VkRGFYTkhRVkZSUW1jM09IZEJVbFZGVWtGNFEyRklVakFLWTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rbDRUak52ZG1KWFRuZGlWemwxVERKR2FtUkhiSFppYmsxMlkyNVdkV041T0hsTlZFMHdUVlJSZVFwT1ZHZDRUbms1YUdSSVVteGlXRUl3WTNrNGVFMUNXVWREYVhOSFFWRlJRbWMzT0hkQlVsbEZRMEYzUjJOSVZtbGlSMnhxVFVsSFNrSm5iM0pDWjBWRkNrRmtXalZCWjFGRFFraHpSV1ZSUWpOQlNGVkJNMVF3ZDJGellraEZWRXBxUjFJMFkyMVhZek5CY1VwTFdISnFaVkJMTXk5b05IQjVaME00Y0Rkdk5FRUtRVUZIWWpremIxbHVRVUZCUWtGTlFWSnFRa1ZCYVVKVlptcHlaVlZIVGpsRlNIRkpWR2N3VjNBd1NtRXZZMGMyUkZCYWRtbGxZV05CZVRkQ016bEtRUXBWZDBsbldHcHNVM0JyTjFVMlNYVTNNVzU0TmsxMlNsQlFXamhRWjJKRE16VlBha295SzJGaE9GZHpOa05pZDNkRFoxbEpTMjlhU1hwcU1FVkJkMDFFQ21GQlFYZGFVVWwzU1VWck5rbEhXSEJKY3pGd2NVbGljM2RDUlVaMVZYSkRTMEp4ZWpGUGMzVkhUamxNVGtWUlZXUlBORkU0WW5WTU9FVjZhbUZEZEVjS1dHRjFhMDFIVkRSQmFrVkJORVo1U0Uxbk1tdHFOMHhtUzJGamRIZHRhVXRzZEdvNFUweGphRVZ4U2tKTldFbDVWRWRMYjJ4SVdUWjFUVmh5VjI0MFJncFJiWEowWjBjd2VUSjBiek1LTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In1dfX0="}]},"envelope":{"statement":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoibWNwbW9uLTAuMS40LXB5My1ub25lLWFueS53aGwiLCJkaWdlc3QiOnsic2hhMjU2IjoiZWU0YjRmODlkMmU3MzI0MGMzODM3ZGJlYTM3ODhkNTEyMDA2ZDRlNmRmZDUyMmYzNWRiMzhmNDM0MWRmMmQxOCJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2RvY3MucHlwaS5vcmcvYXR0ZXN0YXRpb25zL3B1Ymxpc2gvdjEiLCJwcmVkaWNhdGUiOm51bGx9","signature":"MEUCIQDQFzNp2BXavnEjY95H6o7YRuVRJ+Qo4axkrrkypkRIZwIgTVCz4/E/7/DWK2HAw9z+pDNo73nVlr5lZa76WbzLOTM="}}
|
package/dist/mcpmon-0.1.4.tar.gz
DELETED
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"verification_material":{"certificate":"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","transparency_entries":[{"logIndex":"854724714","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"dsse","version":"0.0.1"},"integratedTime":"1769383532","inclusionPromise":{"signedEntryTimestamp":"MEUCIQDZSFWe7ove2wZmYjD9+E4wMIg1pKZyCabS093sxTb1jwIgGQ3wocq5ScAy8WF0PqWS3pBdFSLugsYGKJYR1w71LXk="},"inclusionProof":{"logIndex":"732820452","rootHash":"QqQZjFx9z+R7lB0+3aYwqKyeWxqEpwfolt93Cprx7nQ=","treeSize":"732820453","hashes":["5Lpzt0SDkMvXkzuXQkM4IczaD3YieQihqNFRiJ/0j4I=","74KmBe4Kjlo2YGKnnWSZDq70uawwQKc6lVoDBeIyJZ0=","MZE7wMO38x+IyNk2UeqawjOiRE8nzWZDKxJ4B96w+ro=","8QqJNOeX+oNUh9Jl2kca4qTMqiJPStt58YXQM9mqAHw=","DwKwRQf8bLfvAlZCumHHqyln+KUP87MajXC3gQiRsXk=","rF9MeFdsXSqGF8vqzgxBkGyzLstktTr84yuHfN9eI14=","avGfYpplrUBIQg4ezCF3xrJGXOH8Uh/sOtaaNxjyqmo=","sss5FthkuTnwa241eO0hxXXBCR3QeypHRP73o9RzO9g=","jfukQMAVfCqCrlYwRqqUd/hbEP4yotGKGxR4+c54pfA=","OllZ6+8Si5cuPekJ1g/CP+sXnQHy2JgxYEUmTqicIqc=","dBkbakOCkLCPfwfbc9RJj5FE13JWP1L+K/QnvWbBXjw=","8UxdDATbCIzp+y4X0U33q0Ofz5gDN3girMGDR3eDQes=","i0MObzT1TBsOoj2fSfX5tVy/0RDAzekFRhxMA9dNLRk=","tpLJI2Sg9ZYY8iWiNmofGyoEHdnSBSJwiMmlIBYhOfQ=","O8/imYCTZtbxIc+69c8pb8IaxPdmSK7UPXh/ip+hoLI=","6OLWbtKvxLyT2cr4F2qdHh0quyYiSl/P7LpDfVrK6R8=","WFmkMhmL2tOzDi6lp4zGgaCzwux2vGOM44v1vr1wuDs=","F9MSQ5SmoFr+hoADclpdFY52/TLfHDnNPYb9ZNYO5gI=","T4DqWD42hAtN+vX8jKCWqoC4meE4JekI9LxYGCcPy1M="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n732820453\nQqQZjFx9z+R7lB0+3aYwqKyeWxqEpwfolt93Cprx7nQ=\n\nā rekor.sigstore.dev wNI9ajBFAiBVLMSIaR0zcE5L0lp4GkSwIvzI7yF4azFq0BNIcw5X+gIhAIZlXAewEihLxBFfMpD0RMeEhPPG+1g6B+JIvyWoEXP7\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZGRkZDVkOWQwYmE3YmY4MzQ3ZDgyMmI2NDZhYjI5MTdjODNjMGMwNmE4N2Q5MjU2OTI2MmIxNjE0NjhmZTM2MiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjJkMjAzNjVjYmEwYTBkMzQ0MzAxZDMzZjU0YmM1NzI4YTM0NzhmMTMzNDRlNGVlYjJkY2IyMTUyMjc3ODczZjgifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lEOFRjaVczUnJxODJmVVFtYXcwUDllVVJvUW9VSUJYcHU0WUlyamd4V0VpQWlBYXdTYitxTlp0MjdjYk1jc3VpWlAxOVhvN3JoUzBhdklxUHpZN1pRYld2UT09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1Vkc1ZFTkRRbWgxWjBGM1NVSkJaMGxWVUc5a1lVcEZlRTFTUzNkRFVYVXZVbWgyY0dSMUwxWTJhblpSZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwWmQwMVVTVEZOYWsxNVRsUk5lVmRvWTA1TmFsbDNUVlJKTVUxcVRYcE9WRTE1VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVnFOVFpJTjNBMVRIbDZTR1paTHpaS2Ntc3JMemMzVlhSbVUxUXllbEkxUW1wUlVsY0tVbUZLVkd0NVlYZE9aVE4wVUcxT1ZYaGlaREo0U2xOc2EyMHhhMGxXUVhWVmRUSlBORnBpVDJ4SVVVUmhXbWhJTmpaUFEwSlViM2RuWjFVeVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVkVLM2hKQ21wek9GSm5ZbFE0S3pCa2JuWkthekVyVHpoc1ZITjNkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMWRuV1VSV1VqQlNRVkZJTDBKR1FYZFViMXBOWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBUREpKZUU0emIzWmlWMDUzWWxjNWRRcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFpoUjFab1draE5kbUpYUm5CaWFrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUlNVVkZXR1IyWTIxMGJXSkhPVE5ZTWxKd1l6TkNhR1JIVG05TlJGbEhRMmx6UndwQlVWRkNaemM0ZDBGUlRVVkxSMUV3VGxSSmVWcEhVWGhQVkdkM1RqSk5lVTFxUlRSUFZHUm9XVEphYVUxVWF6Uk5iVlV3VFVSWk0wNXRTVEZOUjFVeENrNUhWWGRHVVZsTFMzZFpRa0pCUjBSMmVrRkNRa0ZSU0ZWdFZuTmFWMFo2V2xSQldrSm5iM0pDWjBWRlFWbFBMMDFCUlVaQ1FYUnBUVlJrTmt3eU1Xb0tZMGN4ZG1KcVFXUkNaMjl5UW1kRlJVRlpUeTlOUVVWSFFrRTVlVnBYV25wTU1taHNXVmRTZWt3eU1XaGhWelIzVDNkWlMwdDNXVUpDUVVkRWRucEJRZ3BEUVZGMFJFTjBiMlJJVW5kamVtOTJURE5TZG1FeVZuVk1iVVpxWkVkc2RtSnVUWFZhTW13d1lVaFdhV1JZVG14amJVNTJZbTVTYkdKdVVYVlpNamwwQ2sxR2QwZERhWE5IUVZGUlFtYzNPSGRCVVd0RlZHZDRUV0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRFd3lTWGhPTTI5MllsZE9kMkpYT1hVS1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTmlNMlI2VEROS2JHSkhWbWhqTWxWMVpWY3hjMUZJU214YWJrMTJZVWRXYUZwSVRYWmlWMFp3WW1wQk5BcENaMjl5UW1kRlJVRlpUeTlOUVVWTFFrTnZUVXRIVVRCT1ZFbDVXa2RSZUU5VVozZE9NazE1VFdwRk5FOVVaR2haTWxwcFRWUnJORTF0VlRCTlJGa3pDazV0U1RGTlIxVXhUa2RWZDBoUldVdExkMWxDUWtGSFJIWjZRVUpEZDFGUVJFRXhibUZZVW05a1YwbDBZVWM1ZW1SSFZtdE5RelJIUTJselIwRlJVVUlLWnpjNGQwRlJkMFZKUVhkbFlVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKSmVFNHpiM1ppVjA1M1lsYzVkVTFFWjBkRGFYTkhRVkZSUWdwbk56aDNRVkV3UlV0bmQyOWFSRkV4VFdwS2ExcEVSVFZQUkVFeldYcEplVTFVWnpWT01rWnFXbTFKZUU5VVozbGFWRkYzVG1wak1sbHFWWGRhVkZVd0NscFVRV1pDWjI5eVFtZEZSVUZaVHk5TlFVVlBRa0pGVFVRelNteGFiazEyWVVkV2FGcElUWFppVjBad1ltcEJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQUtRa0YzVFVOcVJYaE9SRWwzVGxSVk0wOUVSWGRLZDFsTFMzZFpRa0pCUjBSMmVrRkNSVUZSV2tSQ1pHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhZ3BpTWpCMldXcEZNMlZxUVZoQ1oyOXlRbWRGUlVGWlR5OU5RVVZTUWtGclRVSjZUVEpOZWswd1QwUlJkMWhCV1V0TGQxbENRa0ZIUkhaNlFVSkZaMUpQQ2tSRmVHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaWmFrVXpaV2s1ZEZrelFuUmlNalIyVEcxa2NHUkhhREZaYVRrellqTktjbHB0ZUhZS1pETk5kbU50Vm5OYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRsMFdWZHNkVTFFWjBkRGFYTkhRVkZSUW1jM09IZEJVazFGUzJkM2J3cGFSRkV4VFdwS2ExcEVSVFZQUkVFeldYcEplVTFVWnpWT01rWnFXbTFKZUU5VVozbGFWRkYzVG1wak1sbHFWWGRhVkZVd1dsUkJhRUpuYjNKQ1owVkZDa0ZaVHk5TlFVVlZRa0pOVFVWWVpIWmpiWFJ0WWtjNU0xZ3lVbkJqTTBKb1pFZE9iMDFHU1VkRGFYTkhRVkZSUW1jM09IZEJVbFZGVWtGNFEyRklVakFLWTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rbDRUak52ZG1KWFRuZGlWemwxVERKR2FtUkhiSFppYmsxMlkyNVdkV041T0hsTlZFMHdUVlJSZVFwT1ZHZDRUbms1YUdSSVVteGlXRUl3WTNrNGVFMUNXVWREYVhOSFFWRlJRbWMzT0hkQlVsbEZRMEYzUjJOSVZtbGlSMnhxVFVsSFNrSm5iM0pDWjBWRkNrRmtXalZCWjFGRFFraHpSV1ZSUWpOQlNGVkJNMVF3ZDJGellraEZWRXBxUjFJMFkyMVhZek5CY1VwTFdISnFaVkJMTXk5b05IQjVaME00Y0Rkdk5FRUtRVUZIWWpremIxbHVRVUZCUWtGTlFWSnFRa1ZCYVVKVlptcHlaVlZIVGpsRlNIRkpWR2N3VjNBd1NtRXZZMGMyUkZCYWRtbGxZV05CZVRkQ016bEtRUXBWZDBsbldHcHNVM0JyTjFVMlNYVTNNVzU0TmsxMlNsQlFXamhRWjJKRE16VlBha295SzJGaE9GZHpOa05pZDNkRFoxbEpTMjlhU1hwcU1FVkJkMDFFQ21GQlFYZGFVVWwzU1VWck5rbEhXSEJKY3pGd2NVbGljM2RDUlVaMVZYSkRTMEp4ZWpGUGMzVkhUamxNVGtWUlZXUlBORkU0WW5WTU9FVjZhbUZEZEVjS1dHRjFhMDFIVkRSQmFrVkJORVo1U0Uxbk1tdHFOMHhtUzJGamRIZHRhVXRzZEdvNFUweGphRVZ4U2tKTldFbDVWRWRMYjJ4SVdUWjFUVmh5VjI0MFJncFJiWEowWjBjd2VUSjBiek1LTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In1dfX0="}]},"envelope":{"statement":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoibWNwbW9uLTAuMS40LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiI5NWU3ZDQ3NmIwZTA5M2VmNTBlOTg5ZDY5YzU2YzM2NDdiZThlZjZiNTU2YTZhOTg4YjI5ZWY0NjgxMTFkY2IwIn19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vZG9jcy5weXBpLm9yZy9hdHRlc3RhdGlvbnMvcHVibGlzaC92MSIsInByZWRpY2F0ZSI6bnVsbH0=","signature":"MEQCID8TciW3Rrq82fUQmaw0P9eURoQoUIBXpu4YIrjgxWEiAiAawSb+qNZt27cbMcsuiZP19Xo7rhS0avIqPzY7ZQbWvQ=="}}
|