glaip-sdk 0.0.18__py3-none-any.whl → 0.0.19__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 +304 -33
- glaip_sdk/cli/parsers/json_input.py +62 -14
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.19.dist-info}/METADATA +1 -1
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.19.dist-info}/RECORD +6 -6
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.19.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.19.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Authors:
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
import json
|
|
@@ -54,6 +55,201 @@ from glaip_sdk.utils.serialization import (
|
|
|
54
55
|
console = Console()
|
|
55
56
|
|
|
56
57
|
|
|
58
|
+
def _is_sensitive_data(val: Any) -> bool:
|
|
59
|
+
"""Check if value contains sensitive authentication data.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
val: Value to check for sensitive information
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if the value appears to contain sensitive data
|
|
66
|
+
"""
|
|
67
|
+
if not isinstance(val, dict):
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
sensitive_patterns = {"token", "password", "secret", "key", "credential"}
|
|
71
|
+
return any(
|
|
72
|
+
pattern in str(k).lower() for k in val.keys() for pattern in sensitive_patterns
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _format_preview_value(val: Any) -> str:
|
|
77
|
+
"""Format a value for display in update preview with sensitive data redaction.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
val: Value to format
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Formatted string representation of the value
|
|
84
|
+
"""
|
|
85
|
+
if val is None:
|
|
86
|
+
return "[dim]None[/dim]"
|
|
87
|
+
if isinstance(val, dict):
|
|
88
|
+
# Redact sensitive fields in authentication
|
|
89
|
+
if _is_sensitive_data(val):
|
|
90
|
+
redacted = val.copy()
|
|
91
|
+
sensitive_patterns = {"token", "password", "secret", "key", "credential"}
|
|
92
|
+
for k in list(redacted.keys()):
|
|
93
|
+
if any(pattern in k.lower() for pattern in sensitive_patterns):
|
|
94
|
+
redacted[k] = "<REDACTED>"
|
|
95
|
+
return json.dumps(redacted, indent=2)
|
|
96
|
+
return json.dumps(val, indent=2)
|
|
97
|
+
if isinstance(val, str):
|
|
98
|
+
return f'"{val}"' if val else '""'
|
|
99
|
+
return str(val)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _build_empty_override_warnings(empty_fields: list[str]) -> list[str]:
|
|
103
|
+
"""Build warning lines for empty CLI overrides.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
empty_fields: List of field names with empty string overrides
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
List of formatted warning lines
|
|
110
|
+
"""
|
|
111
|
+
if not empty_fields:
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
warnings = [
|
|
115
|
+
"\n[yellow]⚠️ Warning: Empty values provided via CLI will override import values[/yellow]"
|
|
116
|
+
]
|
|
117
|
+
warnings.extend(
|
|
118
|
+
f"- [yellow]{field}: will be set to empty string[/yellow]"
|
|
119
|
+
for field in empty_fields
|
|
120
|
+
)
|
|
121
|
+
return warnings
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _validate_import_payload_fields(import_payload: dict[str, Any]) -> bool:
|
|
125
|
+
"""Validate that import payload contains updatable fields.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
import_payload: Import payload to validate
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
True if payload has updatable fields, False otherwise
|
|
132
|
+
"""
|
|
133
|
+
updatable_fields = {"name", "transport", "description", "config", "authentication"}
|
|
134
|
+
has_updatable = any(field in import_payload for field in updatable_fields)
|
|
135
|
+
|
|
136
|
+
if not has_updatable:
|
|
137
|
+
available_fields = set(import_payload.keys())
|
|
138
|
+
print_markup(
|
|
139
|
+
"[yellow]⚠️ No updatable fields found in import file.[/yellow]\n"
|
|
140
|
+
f"[dim]Found fields: {', '.join(sorted(available_fields))}[/dim]\n"
|
|
141
|
+
f"[dim]Updatable fields: {', '.join(sorted(updatable_fields))}[/dim]"
|
|
142
|
+
)
|
|
143
|
+
return has_updatable
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _build_update_data_from_sources(
|
|
147
|
+
import_payload: dict[str, Any] | None,
|
|
148
|
+
mcp: Any,
|
|
149
|
+
name: str | None,
|
|
150
|
+
transport: str | None,
|
|
151
|
+
description: str | None,
|
|
152
|
+
config: str | None,
|
|
153
|
+
auth: str | None,
|
|
154
|
+
) -> dict[str, Any]:
|
|
155
|
+
"""Build update data from import payload and CLI flags.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
import_payload: Optional import payload
|
|
159
|
+
mcp: Current MCP object
|
|
160
|
+
name: CLI name flag
|
|
161
|
+
transport: CLI transport flag
|
|
162
|
+
description: CLI description flag
|
|
163
|
+
config: CLI config flag
|
|
164
|
+
auth: CLI auth flag
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dictionary with update data
|
|
168
|
+
"""
|
|
169
|
+
update_data = {}
|
|
170
|
+
|
|
171
|
+
# Start with import data if available
|
|
172
|
+
if import_payload:
|
|
173
|
+
updatable_fields = [
|
|
174
|
+
"name",
|
|
175
|
+
"transport",
|
|
176
|
+
"description",
|
|
177
|
+
"config",
|
|
178
|
+
"authentication",
|
|
179
|
+
]
|
|
180
|
+
for field in updatable_fields:
|
|
181
|
+
if field in import_payload:
|
|
182
|
+
update_data[field] = import_payload[field]
|
|
183
|
+
|
|
184
|
+
# CLI flags override import values
|
|
185
|
+
if name is not None:
|
|
186
|
+
update_data["name"] = name
|
|
187
|
+
if transport is not None:
|
|
188
|
+
update_data["transport"] = transport
|
|
189
|
+
if description is not None:
|
|
190
|
+
update_data["description"] = description
|
|
191
|
+
if config is not None:
|
|
192
|
+
parsed_config = parse_json_input(config)
|
|
193
|
+
config_transport = (
|
|
194
|
+
(transport or import_payload.get("transport"))
|
|
195
|
+
if import_payload
|
|
196
|
+
else (transport or getattr(mcp, "transport", None))
|
|
197
|
+
)
|
|
198
|
+
update_data["config"] = validate_mcp_config_structure(
|
|
199
|
+
parsed_config,
|
|
200
|
+
transport=config_transport,
|
|
201
|
+
source="--config",
|
|
202
|
+
)
|
|
203
|
+
if auth is not None:
|
|
204
|
+
parsed_auth = parse_json_input(auth)
|
|
205
|
+
update_data["authentication"] = validate_mcp_auth_structure(
|
|
206
|
+
parsed_auth, source="--auth"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return update_data
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _collect_cli_overrides(
|
|
213
|
+
name: str | None,
|
|
214
|
+
transport: str | None,
|
|
215
|
+
description: str | None,
|
|
216
|
+
config: str | None,
|
|
217
|
+
auth: str | None,
|
|
218
|
+
) -> dict[str, Any]:
|
|
219
|
+
"""Collect CLI flags that were explicitly provided.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
name: CLI name flag
|
|
223
|
+
transport: CLI transport flag
|
|
224
|
+
description: CLI description flag
|
|
225
|
+
config: CLI config flag
|
|
226
|
+
auth: CLI auth flag
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Dictionary of provided CLI overrides
|
|
230
|
+
"""
|
|
231
|
+
cli_overrides = {}
|
|
232
|
+
if name is not None:
|
|
233
|
+
cli_overrides["name"] = name
|
|
234
|
+
if transport is not None:
|
|
235
|
+
cli_overrides["transport"] = transport
|
|
236
|
+
if description is not None:
|
|
237
|
+
cli_overrides["description"] = description
|
|
238
|
+
if config is not None:
|
|
239
|
+
cli_overrides["config"] = config
|
|
240
|
+
if auth is not None:
|
|
241
|
+
cli_overrides["auth"] = auth
|
|
242
|
+
return cli_overrides
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _handle_cli_error(ctx: Any, error: Exception, operation: str) -> None:
|
|
246
|
+
"""Render CLI error once and exit with non-zero status."""
|
|
247
|
+
handle_json_output(ctx, error=error)
|
|
248
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
249
|
+
display_api_error(error, operation)
|
|
250
|
+
ctx.exit(1)
|
|
251
|
+
|
|
252
|
+
|
|
57
253
|
@click.group(name="mcps", no_args_is_help=True)
|
|
58
254
|
def mcps_group() -> None:
|
|
59
255
|
"""MCP management operations.
|
|
@@ -440,10 +636,7 @@ def create(
|
|
|
440
636
|
handle_rich_output(ctx, rich_panel)
|
|
441
637
|
|
|
442
638
|
except Exception as e:
|
|
443
|
-
|
|
444
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
445
|
-
display_api_error(e, "MCP creation")
|
|
446
|
-
raise click.ClickException(str(e))
|
|
639
|
+
_handle_cli_error(ctx, e, "MCP creation")
|
|
447
640
|
|
|
448
641
|
|
|
449
642
|
def _handle_mcp_export(
|
|
@@ -762,9 +955,50 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
762
955
|
raise click.ClickException(str(e))
|
|
763
956
|
|
|
764
957
|
|
|
958
|
+
def _generate_update_preview(
|
|
959
|
+
mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]
|
|
960
|
+
) -> str:
|
|
961
|
+
"""Generate formatted preview of changes for user confirmation.
|
|
962
|
+
|
|
963
|
+
Args:
|
|
964
|
+
mcp: Current MCP object
|
|
965
|
+
update_data: Data that will be sent in update request
|
|
966
|
+
cli_overrides: CLI flags that were explicitly provided
|
|
967
|
+
|
|
968
|
+
Returns:
|
|
969
|
+
Formatted preview string showing old→new values
|
|
970
|
+
"""
|
|
971
|
+
lines = [
|
|
972
|
+
f"\n[bold]The following fields will be updated for MCP '{mcp.name}':[/bold]\n"
|
|
973
|
+
]
|
|
974
|
+
|
|
975
|
+
empty_overrides = []
|
|
976
|
+
|
|
977
|
+
# Show each field that will be updated
|
|
978
|
+
for field, new_value in update_data.items():
|
|
979
|
+
old_value = getattr(mcp, field, None)
|
|
980
|
+
|
|
981
|
+
# Track empty CLI overrides
|
|
982
|
+
if field in cli_overrides and cli_overrides[field] == "":
|
|
983
|
+
empty_overrides.append(field)
|
|
984
|
+
|
|
985
|
+
old_display = _format_preview_value(old_value)
|
|
986
|
+
new_display = _format_preview_value(new_value)
|
|
987
|
+
|
|
988
|
+
lines.append(f"- [cyan]{field}[/cyan]: {old_display} → {new_display}")
|
|
989
|
+
|
|
990
|
+
# Add warnings for empty CLI overrides
|
|
991
|
+
lines.extend(_build_empty_override_warnings(empty_overrides))
|
|
992
|
+
|
|
993
|
+
return "\n".join(lines)
|
|
994
|
+
|
|
995
|
+
|
|
765
996
|
@mcps_group.command()
|
|
766
997
|
@click.argument("mcp_ref")
|
|
767
998
|
@click.option("--name", help="New MCP name")
|
|
999
|
+
@click.option(
|
|
1000
|
+
"--transport", type=click.Choice(["http", "sse"]), help="New transport protocol"
|
|
1001
|
+
)
|
|
768
1002
|
@click.option("--description", help="New description")
|
|
769
1003
|
@click.option(
|
|
770
1004
|
"--config",
|
|
@@ -776,62 +1010,105 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
776
1010
|
"auth",
|
|
777
1011
|
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
778
1012
|
)
|
|
1013
|
+
@click.option(
|
|
1014
|
+
"--import",
|
|
1015
|
+
"import_file",
|
|
1016
|
+
type=click.Path(exists=True, dir_okay=False, readable=True),
|
|
1017
|
+
help="Import MCP configuration from JSON or YAML export",
|
|
1018
|
+
)
|
|
1019
|
+
@click.option("-y", is_flag=True, help="Skip confirmation prompt when using --import")
|
|
779
1020
|
@output_flags()
|
|
780
1021
|
@click.pass_context
|
|
781
1022
|
def update(
|
|
782
1023
|
ctx: Any,
|
|
783
1024
|
mcp_ref: str,
|
|
784
1025
|
name: str | None,
|
|
1026
|
+
transport: str | None,
|
|
785
1027
|
description: str | None,
|
|
786
1028
|
config: str | None,
|
|
787
1029
|
auth: str | None,
|
|
1030
|
+
import_file: str | None,
|
|
1031
|
+
y: bool,
|
|
788
1032
|
) -> None:
|
|
789
1033
|
"""Update an existing MCP with new configuration values.
|
|
790
1034
|
|
|
1035
|
+
You can update an MCP by providing individual fields via CLI options, or by
|
|
1036
|
+
importing from a file and optionally overriding specific fields.
|
|
1037
|
+
|
|
791
1038
|
Args:
|
|
792
1039
|
ctx: Click context containing output format preferences
|
|
793
1040
|
mcp_ref: MCP reference (ID or name)
|
|
794
1041
|
name: New MCP name (optional)
|
|
1042
|
+
transport: New transport protocol (optional)
|
|
795
1043
|
description: New description (optional)
|
|
796
1044
|
config: New JSON configuration string or @file reference (optional)
|
|
797
1045
|
auth: New JSON authentication object or @file reference (optional)
|
|
1046
|
+
import_file: Optional path to import configuration from export file.
|
|
1047
|
+
CLI options override imported values.
|
|
1048
|
+
y: Skip confirmation prompt when using --import
|
|
798
1049
|
|
|
799
1050
|
Raises:
|
|
800
1051
|
ClickException: If MCP not found, JSON invalid, or no fields specified
|
|
801
1052
|
|
|
802
1053
|
Note:
|
|
803
|
-
|
|
804
|
-
|
|
1054
|
+
Must specify either --import OR at least one CLI field.
|
|
1055
|
+
CLI options override imported values when both are specified.
|
|
1056
|
+
Uses PATCH for import-based updates, PUT/PATCH for CLI-only updates.
|
|
1057
|
+
|
|
1058
|
+
Examples:
|
|
1059
|
+
Update with CLI options:
|
|
1060
|
+
aip mcps update my-mcp --name new-name --transport sse
|
|
1061
|
+
|
|
1062
|
+
Import from file:
|
|
1063
|
+
aip mcps update my-mcp --import mcp-export.json
|
|
1064
|
+
|
|
1065
|
+
Import with overrides:
|
|
1066
|
+
aip mcps update my-mcp --import mcp-export.json --name new-name -y
|
|
805
1067
|
"""
|
|
806
1068
|
try:
|
|
807
1069
|
client = get_client(ctx)
|
|
808
1070
|
|
|
1071
|
+
# Validate that at least one update method is provided
|
|
1072
|
+
cli_flags_provided = any(
|
|
1073
|
+
v is not None for v in [name, transport, description, config, auth]
|
|
1074
|
+
)
|
|
1075
|
+
if not import_file and not cli_flags_provided:
|
|
1076
|
+
raise click.ClickException(
|
|
1077
|
+
"No update fields specified. Use --import file or at least one of: "
|
|
1078
|
+
"--name, --transport, --description, --config, --auth"
|
|
1079
|
+
)
|
|
1080
|
+
|
|
809
1081
|
# Resolve MCP using helper function
|
|
810
1082
|
mcp = _resolve_mcp(ctx, client, mcp_ref)
|
|
811
1083
|
|
|
812
|
-
#
|
|
813
|
-
|
|
814
|
-
if
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
source="--config",
|
|
824
|
-
)
|
|
825
|
-
if auth is not None:
|
|
826
|
-
parsed_auth = parse_json_input(auth)
|
|
827
|
-
update_data["authentication"] = validate_mcp_auth_structure(
|
|
828
|
-
parsed_auth, source="--auth"
|
|
829
|
-
)
|
|
1084
|
+
# Load and validate import data if provided
|
|
1085
|
+
import_payload = None
|
|
1086
|
+
if import_file:
|
|
1087
|
+
import_payload = _load_import_ready_payload(import_file)
|
|
1088
|
+
if not _validate_import_payload_fields(import_payload):
|
|
1089
|
+
return
|
|
1090
|
+
|
|
1091
|
+
# Build update data from import and CLI flags
|
|
1092
|
+
update_data = _build_update_data_from_sources(
|
|
1093
|
+
import_payload, mcp, name, transport, description, config, auth
|
|
1094
|
+
)
|
|
830
1095
|
|
|
831
1096
|
if not update_data:
|
|
832
1097
|
raise click.ClickException("No update fields specified")
|
|
833
1098
|
|
|
834
|
-
#
|
|
1099
|
+
# Show confirmation preview for import-based updates (unless -y flag)
|
|
1100
|
+
if import_payload and not y:
|
|
1101
|
+
cli_overrides = _collect_cli_overrides(
|
|
1102
|
+
name, transport, description, config, auth
|
|
1103
|
+
)
|
|
1104
|
+
preview = _generate_update_preview(mcp, update_data, cli_overrides)
|
|
1105
|
+
print_markup(preview)
|
|
1106
|
+
|
|
1107
|
+
if not click.confirm("\nContinue with update?", default=False):
|
|
1108
|
+
print_markup("[yellow]Update cancelled.[/yellow]")
|
|
1109
|
+
return
|
|
1110
|
+
|
|
1111
|
+
# Update MCP
|
|
835
1112
|
with spinner_context(
|
|
836
1113
|
ctx,
|
|
837
1114
|
"[bold blue]Updating MCP…[/bold blue]",
|
|
@@ -843,10 +1120,7 @@ def update(
|
|
|
843
1120
|
handle_rich_output(ctx, display_update_success("MCP", updated_mcp.name))
|
|
844
1121
|
|
|
845
1122
|
except Exception as e:
|
|
846
|
-
|
|
847
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
848
|
-
display_api_error(e, "MCP update")
|
|
849
|
-
raise click.ClickException(str(e))
|
|
1123
|
+
_handle_cli_error(ctx, e, "MCP update")
|
|
850
1124
|
|
|
851
1125
|
|
|
852
1126
|
@mcps_group.command()
|
|
@@ -896,7 +1170,4 @@ def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
|
|
|
896
1170
|
handle_rich_output(ctx, display_deletion_success("MCP", mcp.name))
|
|
897
1171
|
|
|
898
1172
|
except Exception as e:
|
|
899
|
-
|
|
900
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
901
|
-
display_api_error(e, "MCP deletion")
|
|
902
|
-
raise click.ClickException(str(e))
|
|
1173
|
+
_handle_cli_error(ctx, e, "MCP deletion")
|
|
@@ -12,6 +12,25 @@ from pathlib import Path
|
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
14
|
import click
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _looks_like_file_path(value: str) -> bool:
|
|
19
|
+
"""Check if string looks like a file reference.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
value: String to check for file-like patterns
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
True if the string appears to be a file path
|
|
26
|
+
"""
|
|
27
|
+
return (
|
|
28
|
+
value.lower().endswith((".json", ".yaml", ".yml"))
|
|
29
|
+
or "/" in value
|
|
30
|
+
or "\\" in value # Path separators
|
|
31
|
+
or value.startswith(("./", "../")) # Relative paths
|
|
32
|
+
or value.count(".") > 1 # Likely a filename with extension
|
|
33
|
+
)
|
|
15
34
|
|
|
16
35
|
|
|
17
36
|
def _format_file_error(
|
|
@@ -40,16 +59,16 @@ def _format_file_error(
|
|
|
40
59
|
|
|
41
60
|
|
|
42
61
|
def _parse_json_from_file(file_path_str: str) -> Any:
|
|
43
|
-
"""Parse JSON from a file path.
|
|
62
|
+
"""Parse JSON or YAML from a file path.
|
|
44
63
|
|
|
45
64
|
Args:
|
|
46
|
-
file_path_str: Path to the JSON file (without @ prefix).
|
|
65
|
+
file_path_str: Path to the JSON or YAML file (without @ prefix).
|
|
47
66
|
|
|
48
67
|
Returns:
|
|
49
68
|
Parsed dictionary from file.
|
|
50
69
|
|
|
51
70
|
Raises:
|
|
52
|
-
click.ClickException: If file not found, not readable, empty, or invalid
|
|
71
|
+
click.ClickException: If file not found, not readable, empty, or invalid format.
|
|
53
72
|
"""
|
|
54
73
|
# Resolve relative paths against CWD
|
|
55
74
|
file_path = Path(file_path_str)
|
|
@@ -86,18 +105,35 @@ def _parse_json_from_file(file_path_str: str) -> Any:
|
|
|
86
105
|
_format_file_error("File is empty", file_path_str, file_path)
|
|
87
106
|
)
|
|
88
107
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
# Determine file format and parse accordingly
|
|
109
|
+
file_ext = file_path.suffix.lower()
|
|
110
|
+
|
|
111
|
+
if file_ext in [".yaml", ".yml"]:
|
|
112
|
+
# Parse YAML from file content
|
|
113
|
+
try:
|
|
114
|
+
return yaml.safe_load(content)
|
|
115
|
+
except yaml.YAMLError as e:
|
|
116
|
+
raise click.ClickException(
|
|
117
|
+
_format_file_error(
|
|
118
|
+
"Invalid YAML in file",
|
|
119
|
+
file_path_str,
|
|
120
|
+
file_path,
|
|
121
|
+
detail=f"Error: {e}",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
# Default to JSON parsing
|
|
126
|
+
try:
|
|
127
|
+
return json.loads(content)
|
|
128
|
+
except json.JSONDecodeError as e:
|
|
129
|
+
raise click.ClickException(
|
|
130
|
+
_format_file_error(
|
|
131
|
+
"Invalid JSON in file",
|
|
132
|
+
file_path_str,
|
|
133
|
+
file_path,
|
|
134
|
+
detail=f"Error: {e.msg} at line {e.lineno}, column {e.colno}",
|
|
135
|
+
)
|
|
99
136
|
)
|
|
100
|
-
)
|
|
101
137
|
|
|
102
138
|
|
|
103
139
|
def parse_json_input(value: str | None) -> Any:
|
|
@@ -119,6 +155,9 @@ def parse_json_input(value: str | None) -> Any:
|
|
|
119
155
|
>>> parse_json_input('@/path/to/config.json')
|
|
120
156
|
# Returns content of config.json parsed as JSON
|
|
121
157
|
|
|
158
|
+
>>> parse_json_input('/path/to/config.json')
|
|
159
|
+
# Fallback: treats as file path if JSON parsing fails
|
|
160
|
+
|
|
122
161
|
>>> parse_json_input(None)
|
|
123
162
|
None
|
|
124
163
|
"""
|
|
@@ -134,6 +173,15 @@ def parse_json_input(value: str | None) -> Any:
|
|
|
134
173
|
try:
|
|
135
174
|
return json.loads(value)
|
|
136
175
|
except json.JSONDecodeError as e:
|
|
176
|
+
# Check if the value looks like a file path and provide helpful hint
|
|
177
|
+
if _looks_like_file_path(trimmed):
|
|
178
|
+
raise click.ClickException(
|
|
179
|
+
f"Invalid JSON in inline value\n"
|
|
180
|
+
f"Error: {e.msg} at line {e.lineno}, column {e.colno}\n"
|
|
181
|
+
f"\n💡 Did you mean to load this from a file? "
|
|
182
|
+
f"File-based config values should start with @ (e.g., @{trimmed})"
|
|
183
|
+
)
|
|
184
|
+
|
|
137
185
|
raise click.ClickException(
|
|
138
186
|
f"Invalid JSON in inline value\n"
|
|
139
187
|
f"Error: {e.msg} at line {e.lineno}, column {e.colno}"
|
|
@@ -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=FtWGhbl4QRlqxXFNMEnZUpw5mjQ0KPjY0_6o0hYyoaU,41251
|
|
9
9
|
glaip_sdk/cli/commands/configure.py,sha256=h2GgBpnBWYHAhp3zqkAiy8QNgwPD_7pToZBZRpuMoNM,7869
|
|
10
|
-
glaip_sdk/cli/commands/mcps.py,sha256=
|
|
10
|
+
glaip_sdk/cli/commands/mcps.py,sha256=wqUbkQ6qUUhr6B0n2jJxPUkbxHwfsEnYvdLKew_qorM,36800
|
|
11
11
|
glaip_sdk/cli/commands/models.py,sha256=G1ce-wZOfvMP6SMnIVuSQ89CF444Kz8Ja6nrNOQXCqU,1729
|
|
12
12
|
glaip_sdk/cli/commands/tools.py,sha256=YfkB7HRBGcAOC6N-wXTV5Ch5XTXqjKTtyq-Cfb0-18c,18908
|
|
13
13
|
glaip_sdk/cli/config.py,sha256=jCLJxTBAnOU6EJI6JjcpwUTEAWCJRoALbMrhOvvAofc,946
|
|
@@ -19,7 +19,7 @@ glaip_sdk/cli/masking.py,sha256=BOZjwUqxQf3LQlYgUMwq7UYgve8x4_1Qk04ixiJJPZ8,4399
|
|
|
19
19
|
glaip_sdk/cli/mcp_validators.py,sha256=SeDbgXkRuBXyDtCmUMpL-1Vh7fmGldz-shAaHhOqbCc,10125
|
|
20
20
|
glaip_sdk/cli/pager.py,sha256=n9ypOGPPSaseJlwPG1X38qSz1yV3pjRWunzA4xx5E7M,8052
|
|
21
21
|
glaip_sdk/cli/parsers/__init__.py,sha256=Ycd4HDfYmA7GUGFt0ndBPBo5uTbv15XsXnYUj-a89ug,183
|
|
22
|
-
glaip_sdk/cli/parsers/json_input.py,sha256=
|
|
22
|
+
glaip_sdk/cli/parsers/json_input.py,sha256=ZBhJNUR4bKDX3A5s9lRvuCicaztvQyP0lWuiNYMIO08,5721
|
|
23
23
|
glaip_sdk/cli/resolution.py,sha256=jXUNpKKhs30n7Ke0uz1Hbny5DTo2_sxvchIhTbeBubE,2393
|
|
24
24
|
glaip_sdk/cli/rich_helpers.py,sha256=ByUOmK16IisoXWE7nEiI55BF1KWDrm6KCYAxqHu0XOU,825
|
|
25
25
|
glaip_sdk/cli/slash/__init__.py,sha256=Vdv6Y8bu-pA8dxDlyP4XrhudBPivztUozhLAz9vaLig,682
|
|
@@ -67,7 +67,7 @@ glaip_sdk/utils/rich_utils.py,sha256=-Ij-1bIJvnVAi6DrfftchIlMcvOTjVmSE0Qqax0EY_s
|
|
|
67
67
|
glaip_sdk/utils/run_renderer.py,sha256=d_VMI6LbvHPUUeRmGqh5wK_lHqDEIAcym2iqpbtDad0,1365
|
|
68
68
|
glaip_sdk/utils/serialization.py,sha256=AFbucakFaCtQDfcgsm2gHZ1iZDA8OaJSZUsS6FhWFR0,12820
|
|
69
69
|
glaip_sdk/utils/validation.py,sha256=QNORcdyvuliEs4EH2_mkDgmoyT9utgl7YNhaf45SEf8,6992
|
|
70
|
-
glaip_sdk-0.0.
|
|
71
|
-
glaip_sdk-0.0.
|
|
72
|
-
glaip_sdk-0.0.
|
|
73
|
-
glaip_sdk-0.0.
|
|
70
|
+
glaip_sdk-0.0.19.dist-info/METADATA,sha256=CxTXRqseeY6CsuhudOT_ddbxuK5WLfkkM6oSaaMSxdY,5164
|
|
71
|
+
glaip_sdk-0.0.19.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
72
|
+
glaip_sdk-0.0.19.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
|
|
73
|
+
glaip_sdk-0.0.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|