glaip-sdk 0.0.12__py3-none-any.whl → 0.0.14__py3-none-any.whl
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.
- glaip_sdk/cli/commands/mcps.py +246 -41
- {glaip_sdk-0.0.12.dist-info → glaip_sdk-0.0.14.dist-info}/METADATA +7 -7
- {glaip_sdk-0.0.12.dist-info → glaip_sdk-0.0.14.dist-info}/RECORD +5 -5
- {glaip_sdk-0.0.12.dist-info → glaip_sdk-0.0.14.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.12.dist-info → glaip_sdk-0.0.14.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -24,6 +24,7 @@ from glaip_sdk.cli.display import (
|
|
|
24
24
|
)
|
|
25
25
|
from glaip_sdk.cli.io import (
|
|
26
26
|
fetch_raw_resource_details,
|
|
27
|
+
load_resource_from_file_with_validation,
|
|
27
28
|
)
|
|
28
29
|
from glaip_sdk.cli.mcp_validators import (
|
|
29
30
|
validate_mcp_auth_structure,
|
|
@@ -41,8 +42,12 @@ from glaip_sdk.cli.utils import (
|
|
|
41
42
|
output_result,
|
|
42
43
|
spinner_context,
|
|
43
44
|
)
|
|
45
|
+
from glaip_sdk.config.constants import (
|
|
46
|
+
DEFAULT_MCP_TYPE,
|
|
47
|
+
)
|
|
44
48
|
from glaip_sdk.rich_components import AIPPanel
|
|
45
49
|
from glaip_sdk.utils import format_datetime
|
|
50
|
+
from glaip_sdk.utils.import_export import convert_export_to_import_format
|
|
46
51
|
from glaip_sdk.utils.serialization import (
|
|
47
52
|
build_mcp_export_payload,
|
|
48
53
|
write_resource_export,
|
|
@@ -90,6 +95,180 @@ def _resolve_mcp(
|
|
|
90
95
|
)
|
|
91
96
|
|
|
92
97
|
|
|
98
|
+
def _strip_server_only_fields(import_data: dict[str, Any]) -> dict[str, Any]:
|
|
99
|
+
"""Remove fields that should not be forwarded during import-driven creation.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
import_data: Raw import payload loaded from disk.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A shallow copy of the data with server-managed fields removed.
|
|
106
|
+
"""
|
|
107
|
+
cleaned = dict(import_data)
|
|
108
|
+
for key in (
|
|
109
|
+
"id",
|
|
110
|
+
"type",
|
|
111
|
+
"status",
|
|
112
|
+
"connection_status",
|
|
113
|
+
"created_at",
|
|
114
|
+
"updated_at",
|
|
115
|
+
):
|
|
116
|
+
cleaned.pop(key, None)
|
|
117
|
+
return cleaned
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _load_import_ready_payload(import_file: str) -> dict[str, Any]:
|
|
121
|
+
"""Load and normalise an imported MCP definition for create operations.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
import_file: Path to an MCP export file (JSON or YAML).
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Normalised import payload ready for CLI/REST usage.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
click.ClickException: If the file cannot be parsed or validated.
|
|
131
|
+
"""
|
|
132
|
+
raw_data = load_resource_from_file_with_validation(Path(import_file), "MCP")
|
|
133
|
+
import_data = convert_export_to_import_format(raw_data)
|
|
134
|
+
import_data = _strip_server_only_fields(import_data)
|
|
135
|
+
|
|
136
|
+
transport = import_data.get("transport")
|
|
137
|
+
|
|
138
|
+
if "config" in import_data:
|
|
139
|
+
import_data["config"] = validate_mcp_config_structure(
|
|
140
|
+
import_data["config"],
|
|
141
|
+
transport=transport,
|
|
142
|
+
source="import file",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if "authentication" in import_data:
|
|
146
|
+
import_data["authentication"] = validate_mcp_auth_structure(
|
|
147
|
+
import_data["authentication"],
|
|
148
|
+
source="import file",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return import_data
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _coerce_cli_string(value: str | None) -> str | None:
|
|
155
|
+
"""Normalise CLI string values so blanks are treated as missing.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
value: User-provided string option.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The stripped string, or ``None`` when the value is blank/whitespace-only.
|
|
162
|
+
"""
|
|
163
|
+
if value is None:
|
|
164
|
+
return None
|
|
165
|
+
trimmed = value.strip()
|
|
166
|
+
# Treat whitespace-only strings as None
|
|
167
|
+
return trimmed if trimmed else None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _merge_config_field(
|
|
171
|
+
merged_base: dict[str, Any],
|
|
172
|
+
cli_config: str | None,
|
|
173
|
+
final_transport: str | None,
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Merge config field with validation.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
merged_base: Base payload to update in-place.
|
|
179
|
+
cli_config: Raw CLI JSON string for config.
|
|
180
|
+
final_transport: Transport type for validation.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
click.ClickException: If config JSON parsing or validation fails.
|
|
184
|
+
"""
|
|
185
|
+
if cli_config is not None:
|
|
186
|
+
parsed_config = parse_json_input(cli_config)
|
|
187
|
+
merged_base["config"] = validate_mcp_config_structure(
|
|
188
|
+
parsed_config,
|
|
189
|
+
transport=final_transport,
|
|
190
|
+
source="--config",
|
|
191
|
+
)
|
|
192
|
+
elif "config" not in merged_base or merged_base["config"] is None:
|
|
193
|
+
merged_base["config"] = {}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _merge_auth_field(
|
|
197
|
+
merged_base: dict[str, Any],
|
|
198
|
+
cli_auth: str | None,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Merge authentication field with validation.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
merged_base: Base payload to update in-place.
|
|
204
|
+
cli_auth: Raw CLI JSON string for authentication.
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
click.ClickException: If auth JSON parsing or validation fails.
|
|
208
|
+
"""
|
|
209
|
+
if cli_auth is not None:
|
|
210
|
+
parsed_auth = parse_json_input(cli_auth)
|
|
211
|
+
merged_base["authentication"] = validate_mcp_auth_structure(
|
|
212
|
+
parsed_auth,
|
|
213
|
+
source="--auth",
|
|
214
|
+
)
|
|
215
|
+
elif "authentication" not in merged_base:
|
|
216
|
+
merged_base["authentication"] = None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _merge_import_payload(
|
|
220
|
+
import_data: dict[str, Any] | None,
|
|
221
|
+
*,
|
|
222
|
+
cli_name: str | None,
|
|
223
|
+
cli_transport: str | None,
|
|
224
|
+
cli_description: str | None,
|
|
225
|
+
cli_config: str | None,
|
|
226
|
+
cli_auth: str | None,
|
|
227
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
228
|
+
"""Merge import data with CLI overrides while tracking missing fields.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
import_data: Normalised payload loaded from file (if provided).
|
|
232
|
+
cli_name: Name supplied via CLI option.
|
|
233
|
+
cli_transport: Transport supplied via CLI option.
|
|
234
|
+
cli_description: Description supplied via CLI option.
|
|
235
|
+
cli_config: Raw CLI JSON string for config.
|
|
236
|
+
cli_auth: Raw CLI JSON string for authentication.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
A tuple of (merged_payload, missing_required_fields).
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
click.ClickException: If config/auth JSON parsing or validation fails.
|
|
243
|
+
"""
|
|
244
|
+
merged_base = import_data.copy() if import_data else {}
|
|
245
|
+
|
|
246
|
+
# Merge simple string fields using truthy CLI overrides
|
|
247
|
+
for field, cli_value in (
|
|
248
|
+
("name", _coerce_cli_string(cli_name)),
|
|
249
|
+
("transport", _coerce_cli_string(cli_transport)),
|
|
250
|
+
("description", _coerce_cli_string(cli_description)),
|
|
251
|
+
):
|
|
252
|
+
if cli_value is not None:
|
|
253
|
+
merged_base[field] = cli_value
|
|
254
|
+
|
|
255
|
+
# Determine final transport before validating config
|
|
256
|
+
final_transport = merged_base.get("transport")
|
|
257
|
+
|
|
258
|
+
# Merge config and authentication with validation
|
|
259
|
+
_merge_config_field(merged_base, cli_config, final_transport)
|
|
260
|
+
_merge_auth_field(merged_base, cli_auth)
|
|
261
|
+
|
|
262
|
+
# Validate required fields
|
|
263
|
+
missing_fields = []
|
|
264
|
+
for required in ("name", "transport"):
|
|
265
|
+
value = merged_base.get(required)
|
|
266
|
+
if not isinstance(value, str) or not value.strip():
|
|
267
|
+
missing_fields.append(required)
|
|
268
|
+
|
|
269
|
+
return merged_base, missing_fields
|
|
270
|
+
|
|
271
|
+
|
|
93
272
|
@mcps_group.command(name="list")
|
|
94
273
|
@output_flags()
|
|
95
274
|
@click.pass_context
|
|
@@ -139,8 +318,8 @@ def list_mcps(ctx: Any) -> None:
|
|
|
139
318
|
|
|
140
319
|
|
|
141
320
|
@mcps_group.command()
|
|
142
|
-
@click.option("--name",
|
|
143
|
-
@click.option("--transport",
|
|
321
|
+
@click.option("--name", help="MCP name")
|
|
322
|
+
@click.option("--transport", help="MCP transport protocol")
|
|
144
323
|
@click.option("--description", help="MCP description")
|
|
145
324
|
@click.option(
|
|
146
325
|
"--config",
|
|
@@ -152,72 +331,100 @@ def list_mcps(ctx: Any) -> None:
|
|
|
152
331
|
"auth",
|
|
153
332
|
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
154
333
|
)
|
|
334
|
+
@click.option(
|
|
335
|
+
"--import",
|
|
336
|
+
"import_file",
|
|
337
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
338
|
+
help="Import MCP configuration from JSON or YAML export",
|
|
339
|
+
)
|
|
155
340
|
@output_flags()
|
|
156
341
|
@click.pass_context
|
|
157
342
|
def create(
|
|
158
343
|
ctx: Any,
|
|
159
|
-
name: str,
|
|
160
|
-
transport: str,
|
|
344
|
+
name: str | None,
|
|
345
|
+
transport: str | None,
|
|
161
346
|
description: str | None,
|
|
162
347
|
config: str | None,
|
|
163
348
|
auth: str | None,
|
|
349
|
+
import_file: str | None,
|
|
164
350
|
) -> None:
|
|
165
351
|
"""Create a new MCP with specified configuration.
|
|
166
352
|
|
|
353
|
+
You can create an MCP by providing all parameters via CLI options, or by
|
|
354
|
+
importing from a file and optionally overriding specific fields.
|
|
355
|
+
|
|
167
356
|
Args:
|
|
168
357
|
ctx: Click context containing output format preferences
|
|
169
|
-
name: MCP name (required)
|
|
170
|
-
transport: MCP transport protocol (required)
|
|
358
|
+
name: MCP name (required unless provided via --import)
|
|
359
|
+
transport: MCP transport protocol (required unless provided via --import)
|
|
171
360
|
description: Optional MCP description
|
|
172
361
|
config: JSON configuration string or @file reference
|
|
173
362
|
auth: JSON authentication object or @file reference
|
|
363
|
+
import_file: Optional path to import configuration from export file.
|
|
364
|
+
CLI options override imported values.
|
|
174
365
|
|
|
175
366
|
Raises:
|
|
176
367
|
ClickException: If JSON parsing fails or API request fails
|
|
368
|
+
|
|
369
|
+
Examples:
|
|
370
|
+
Create from CLI options:
|
|
371
|
+
aip mcps create --name my-mcp --transport http --config '{"url": "https://api.example.com"}'
|
|
372
|
+
|
|
373
|
+
Import from file:
|
|
374
|
+
aip mcps create --import mcp-export.json
|
|
375
|
+
|
|
376
|
+
Import with overrides:
|
|
377
|
+
aip mcps create --import mcp-export.json --name new-name --transport sse
|
|
177
378
|
"""
|
|
178
379
|
try:
|
|
179
380
|
client = get_client(ctx)
|
|
180
381
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if raw_config is None:
|
|
184
|
-
mcp_config: dict[str, Any] = {}
|
|
185
|
-
else:
|
|
186
|
-
mcp_config = validate_mcp_config_structure(
|
|
187
|
-
raw_config,
|
|
188
|
-
transport=transport,
|
|
189
|
-
source="--config",
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
# Parse authentication if provided (supports inline JSON or @file)
|
|
193
|
-
mcp_auth = parse_json_input(auth)
|
|
194
|
-
validated_auth = (
|
|
195
|
-
validate_mcp_auth_structure(mcp_auth, source="--auth")
|
|
196
|
-
if mcp_auth is not None
|
|
197
|
-
else None
|
|
382
|
+
import_payload = (
|
|
383
|
+
_load_import_ready_payload(import_file) if import_file is not None else None
|
|
198
384
|
)
|
|
199
385
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
386
|
+
merged_payload, missing_fields = _merge_import_payload(
|
|
387
|
+
import_payload,
|
|
388
|
+
cli_name=name,
|
|
389
|
+
cli_transport=transport,
|
|
390
|
+
cli_description=description,
|
|
391
|
+
cli_config=config,
|
|
392
|
+
cli_auth=auth,
|
|
393
|
+
)
|
|
207
394
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
395
|
+
if missing_fields:
|
|
396
|
+
raise click.ClickException(
|
|
397
|
+
"Missing required fields after combining import and CLI values: "
|
|
398
|
+
+ ", ".join(missing_fields)
|
|
399
|
+
)
|
|
211
400
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
401
|
+
effective_name = merged_payload["name"]
|
|
402
|
+
effective_transport = merged_payload["transport"]
|
|
403
|
+
effective_description = merged_payload.get("description")
|
|
404
|
+
effective_config = merged_payload.get("config") or {}
|
|
405
|
+
effective_auth = merged_payload.get("authentication")
|
|
215
406
|
|
|
216
407
|
with spinner_context(
|
|
217
408
|
ctx,
|
|
218
409
|
"[bold blue]Creating MCP…[/bold blue]",
|
|
219
410
|
console_override=console,
|
|
220
411
|
):
|
|
412
|
+
create_kwargs: dict[str, Any] = {
|
|
413
|
+
"name": effective_name,
|
|
414
|
+
"config": effective_config,
|
|
415
|
+
"transport": effective_transport,
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if effective_description is not None:
|
|
419
|
+
create_kwargs["description"] = effective_description
|
|
420
|
+
|
|
421
|
+
if effective_auth:
|
|
422
|
+
create_kwargs["authentication"] = effective_auth
|
|
423
|
+
|
|
424
|
+
mcp_metadata = merged_payload.get("mcp_metadata")
|
|
425
|
+
if mcp_metadata is not None:
|
|
426
|
+
create_kwargs["mcp_metadata"] = mcp_metadata
|
|
427
|
+
|
|
221
428
|
mcp = client.mcps.create_mcp(**create_kwargs)
|
|
222
429
|
|
|
223
430
|
# Handle JSON output
|
|
@@ -228,9 +435,9 @@ def create(
|
|
|
228
435
|
"MCP",
|
|
229
436
|
mcp.name,
|
|
230
437
|
mcp.id,
|
|
231
|
-
Type="
|
|
232
|
-
Transport=getattr(mcp, "transport",
|
|
233
|
-
Description=
|
|
438
|
+
Type=getattr(mcp, "type", DEFAULT_MCP_TYPE),
|
|
439
|
+
Transport=getattr(mcp, "transport", effective_transport),
|
|
440
|
+
Description=effective_description or "No description",
|
|
234
441
|
)
|
|
235
442
|
handle_rich_output(ctx, rich_panel)
|
|
236
443
|
|
|
@@ -478,7 +685,6 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
478
685
|
columns = [
|
|
479
686
|
("name", "Name", "cyan", None),
|
|
480
687
|
("description", "Description", "green", 50),
|
|
481
|
-
("type", "Type", "yellow", None),
|
|
482
688
|
]
|
|
483
689
|
|
|
484
690
|
# Transform function for safe dictionary access
|
|
@@ -488,7 +694,6 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
488
694
|
"description": tool.get("description", "N/A")[:47] + "..."
|
|
489
695
|
if len(tool.get("description", "")) > 47
|
|
490
696
|
else tool.get("description", "N/A"),
|
|
491
|
-
"type": tool.get("type", "N/A"),
|
|
492
697
|
}
|
|
493
698
|
|
|
494
699
|
output_list(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: glaip-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.14
|
|
4
4
|
Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Raymond Christopher
|
|
@@ -172,13 +172,13 @@ aip agents run <AGENT_ID> --input "Hello world, what's the weather like?"
|
|
|
172
172
|
|
|
173
173
|
## 📚 Documentation
|
|
174
174
|
|
|
175
|
-
📖 **[Complete Documentation](https://gdplabs.gitbook.io/
|
|
175
|
+
📖 **[Complete Documentation](https://gdplabs.gitbook.io/gl-aip/gl-aip-sdk/overview)** - Visit our GitBook for comprehensive guides, tutorials, and API reference.
|
|
176
176
|
|
|
177
177
|
Quick links:
|
|
178
178
|
|
|
179
|
-
- **[Quick Start Guide](
|
|
180
|
-
- **[Agent Management](
|
|
181
|
-
- **[Custom Tools](
|
|
182
|
-
- **[MCP Integration](
|
|
183
|
-
- **[API Reference](
|
|
179
|
+
- **[Quick Start Guide](https://gdplabs.gitbook.io/gl-aip/gl-aip-sdk/get-started/quick-start-guide)**: Get your first agent running in 5 minutes
|
|
180
|
+
- **[Agent Management](https://gdplabs.gitbook.io/gl-aip/gl-aip-sdk/guides/agents-guide)**: Complete agent lifecycle management
|
|
181
|
+
- **[Custom Tools](https://gdplabs.gitbook.io/gl-aip/gl-aip-sdk/guides/tools-guide)**: Build and integrate custom tools
|
|
182
|
+
- **[MCP Integration](https://gdplabs.gitbook.io/gl-aip/gl-aip-sdk/guides/mcps-guide)**: Connect external services
|
|
183
|
+
- **[API Reference](https://gdplabs.gitbook.io/gl-aip/gl-aip-sdk/reference/python-sdk-reference)**: Complete SDK reference
|
|
184
184
|
|
|
@@ -7,7 +7,7 @@ glaip_sdk/cli/auth.py,sha256=eYdtGmJ3XgiO96hq_69GF6b3W-aRWZrDQ-6bHuaRX4M,13517
|
|
|
7
7
|
glaip_sdk/cli/commands/__init__.py,sha256=x0CZlZbZHoHvuzfoTWIyEch6WmNnbPzxajrox6riYp0,173
|
|
8
8
|
glaip_sdk/cli/commands/agents.py,sha256=97dzowjHgk5knyHuI-0z2ojvqNlkebNN1-ikGEoS5sc,40623
|
|
9
9
|
glaip_sdk/cli/commands/configure.py,sha256=eRDzsaKV4fl2lJt8ieS4g2-xRnaa02eAAPW8xBf-tqA,7507
|
|
10
|
-
glaip_sdk/cli/commands/mcps.py,sha256=
|
|
10
|
+
glaip_sdk/cli/commands/mcps.py,sha256=ENhasfSupmCSKs-Ycg-M9Gy-58Y55SMIQzeg3fBJj48,28186
|
|
11
11
|
glaip_sdk/cli/commands/models.py,sha256=Ra3-50BPScNs0Q-j4b7U4iK0hNooucEyVgHpQ11-pt8,1700
|
|
12
12
|
glaip_sdk/cli/commands/tools.py,sha256=MOM9Db3HGL1stF-WvL5cZXjw-iZo2qc-oyKQHy6VwIM,18690
|
|
13
13
|
glaip_sdk/cli/display.py,sha256=jE20swoRKzpYUmc0jgbeonaXKeE9x95hfjWAEdnBYRc,8727
|
|
@@ -58,7 +58,7 @@ glaip_sdk/utils/rich_utils.py,sha256=-Ij-1bIJvnVAi6DrfftchIlMcvOTjVmSE0Qqax0EY_s
|
|
|
58
58
|
glaip_sdk/utils/run_renderer.py,sha256=d_VMI6LbvHPUUeRmGqh5wK_lHqDEIAcym2iqpbtDad0,1365
|
|
59
59
|
glaip_sdk/utils/serialization.py,sha256=T1yt_8G2DCFpcxx7XnqFl5slksRXfBCUuLQJTreGYEQ,11806
|
|
60
60
|
glaip_sdk/utils/validation.py,sha256=QNORcdyvuliEs4EH2_mkDgmoyT9utgl7YNhaf45SEf8,6992
|
|
61
|
-
glaip_sdk-0.0.
|
|
62
|
-
glaip_sdk-0.0.
|
|
63
|
-
glaip_sdk-0.0.
|
|
64
|
-
glaip_sdk-0.0.
|
|
61
|
+
glaip_sdk-0.0.14.dist-info/METADATA,sha256=hLG4fJ5u_23gDgWSYD1THNIIRgkBh1i9OTp76TJMCzo,5168
|
|
62
|
+
glaip_sdk-0.0.14.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
63
|
+
glaip_sdk-0.0.14.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
|
|
64
|
+
glaip_sdk-0.0.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|