systemlink-cli 1.3.1__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.
Files changed (74) hide show
  1. slcli/__init__.py +1 -0
  2. slcli/__main__.py +23 -0
  3. slcli/_version.py +4 -0
  4. slcli/asset_click.py +1289 -0
  5. slcli/cli_formatters.py +218 -0
  6. slcli/cli_utils.py +504 -0
  7. slcli/comment_click.py +602 -0
  8. slcli/completion_click.py +418 -0
  9. slcli/config.py +81 -0
  10. slcli/config_click.py +498 -0
  11. slcli/dff_click.py +979 -0
  12. slcli/dff_decorators.py +24 -0
  13. slcli/example_click.py +404 -0
  14. slcli/example_loader.py +274 -0
  15. slcli/example_provisioner.py +2777 -0
  16. slcli/examples/README.md +134 -0
  17. slcli/examples/_schema/schema-v1.0.json +169 -0
  18. slcli/examples/demo-complete-workflow/README.md +323 -0
  19. slcli/examples/demo-complete-workflow/config.yaml +638 -0
  20. slcli/examples/demo-test-plans/README.md +132 -0
  21. slcli/examples/demo-test-plans/config.yaml +154 -0
  22. slcli/examples/exercise-5-1-parametric-insights/README.md +101 -0
  23. slcli/examples/exercise-5-1-parametric-insights/config.yaml +1589 -0
  24. slcli/examples/exercise-7-1-test-plans/README.md +93 -0
  25. slcli/examples/exercise-7-1-test-plans/config.yaml +323 -0
  26. slcli/examples/spec-compliance-notebooks/README.md +140 -0
  27. slcli/examples/spec-compliance-notebooks/config.yaml +112 -0
  28. slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +1553 -0
  29. slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +1577 -0
  30. slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +912 -0
  31. slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  32. slcli/feed_click.py +892 -0
  33. slcli/file_click.py +932 -0
  34. slcli/function_click.py +1400 -0
  35. slcli/function_templates.py +85 -0
  36. slcli/main.py +406 -0
  37. slcli/mcp_click.py +269 -0
  38. slcli/mcp_server.py +748 -0
  39. slcli/notebook_click.py +1770 -0
  40. slcli/platform.py +345 -0
  41. slcli/policy_click.py +679 -0
  42. slcli/policy_utils.py +411 -0
  43. slcli/profiles.py +411 -0
  44. slcli/response_handlers.py +359 -0
  45. slcli/routine_click.py +763 -0
  46. slcli/skill_click.py +253 -0
  47. slcli/skills/slcli/SKILL.md +713 -0
  48. slcli/skills/slcli/references/analysis-recipes.md +474 -0
  49. slcli/skills/slcli/references/filtering.md +236 -0
  50. slcli/skills/systemlink-webapp/SKILL.md +744 -0
  51. slcli/skills/systemlink-webapp/references/deployment.md +123 -0
  52. slcli/skills/systemlink-webapp/references/nimble-angular.md +380 -0
  53. slcli/skills/systemlink-webapp/references/systemlink-services.md +192 -0
  54. slcli/ssl_trust.py +93 -0
  55. slcli/system_click.py +2216 -0
  56. slcli/table_utils.py +124 -0
  57. slcli/tag_click.py +794 -0
  58. slcli/templates_click.py +599 -0
  59. slcli/testmonitor_click.py +1667 -0
  60. slcli/universal_handlers.py +305 -0
  61. slcli/user_click.py +1218 -0
  62. slcli/utils.py +832 -0
  63. slcli/web_editor.py +295 -0
  64. slcli/webapp_click.py +981 -0
  65. slcli/workflow_preview.py +287 -0
  66. slcli/workflows_click.py +988 -0
  67. slcli/workitem_click.py +2258 -0
  68. slcli/workspace_click.py +576 -0
  69. slcli/workspace_utils.py +206 -0
  70. systemlink_cli-1.3.1.dist-info/METADATA +20 -0
  71. systemlink_cli-1.3.1.dist-info/RECORD +74 -0
  72. systemlink_cli-1.3.1.dist-info/WHEEL +4 -0
  73. systemlink_cli-1.3.1.dist-info/entry_points.txt +7 -0
  74. systemlink_cli-1.3.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,305 @@
1
+ """Enhanced response handlers for all CLI commands."""
2
+
3
+ import json
4
+ import sys
5
+ from typing import Dict, List, Any, Optional, Union, Callable
6
+
7
+ import click
8
+ import requests
9
+
10
+ from .utils import ExitCodes, handle_api_error, format_success
11
+
12
+
13
+ class FilteredResponse:
14
+ """Mock response class for use with UniversalResponseHandler.
15
+
16
+ This class mimics a requests.Response object to allow filtered data
17
+ to be passed to UniversalResponseHandler without making additional API calls.
18
+ """
19
+
20
+ def __init__(self, data: Dict[str, Any], status_code: int = 200):
21
+ """Initialize with data and optional status code.
22
+
23
+ Args:
24
+ data: Dictionary containing the response data
25
+ status_code: HTTP status code to return (default: 200)
26
+ """
27
+ self._data = data
28
+ self._status_code = status_code
29
+
30
+ def json(self) -> Dict[str, Any]:
31
+ """Return the response data as JSON."""
32
+ return self._data
33
+
34
+ @property
35
+ def status_code(self) -> int:
36
+ """Return the HTTP status code."""
37
+ return self._status_code
38
+
39
+
40
+ class UniversalResponseHandler:
41
+ """Universal response handler for all CLI commands."""
42
+
43
+ @staticmethod
44
+ def handle_list_response(
45
+ resp: Union[requests.Response, "FilteredResponse"],
46
+ data_key: str,
47
+ item_name: str,
48
+ format_output: str,
49
+ formatter_func: Optional[Callable[[Dict[str, Any]], List[str]]] = None,
50
+ headers: Optional[List[str]] = None,
51
+ column_widths: Optional[List[int]] = None,
52
+ empty_message: Optional[str] = None,
53
+ enable_pagination: bool = True,
54
+ page_size: int = 25,
55
+ total_count: Optional[int] = None,
56
+ shown_count: Optional[int] = None,
57
+ ) -> None:
58
+ """Handle list response with standardized formatting and optional pagination.
59
+
60
+ Args:
61
+ resp: API response
62
+ data_key: Key to extract items from response
63
+ item_name: Name of item type (for messages)
64
+ format_output: 'table' or 'json'
65
+ formatter_func: Function to format table rows
66
+ headers: Table headers
67
+ column_widths: Table column widths
68
+ empty_message: Message when no items found
69
+ enable_pagination: Whether to enable pagination for table output
70
+ page_size: Number of items per page
71
+ total_count: Optional server-provided total count for all matching items.
72
+ shown_count: Optional count of items shown so far (useful for paged responses).
73
+ """
74
+ from .cli_utils import paginate_list_output
75
+ from .table_utils import output_formatted_list
76
+
77
+ try:
78
+ data = resp.json()
79
+ # Support both dict with data_key and direct array responses
80
+ if isinstance(data, list):
81
+ items = data
82
+ elif isinstance(data, dict):
83
+ items = data.get(data_key, [])
84
+ else:
85
+ items = []
86
+
87
+ if not empty_message:
88
+ empty_message = f"No {item_name}s found."
89
+
90
+ if enable_pagination and format_output.lower() == "table":
91
+ # Use pagination for table output
92
+ paginate_list_output(
93
+ items,
94
+ page_size=page_size,
95
+ format_output=format_output,
96
+ formatter_func=formatter_func,
97
+ headers=headers,
98
+ column_widths=column_widths,
99
+ empty_message=empty_message,
100
+ total_label=f"{item_name}(s)",
101
+ )
102
+ elif format_output.lower() == "json":
103
+ # For JSON format, always output all items (no display pagination)
104
+ click.echo(json.dumps(items, indent=2))
105
+ elif formatter_func and headers and column_widths:
106
+ # Use traditional output (no pagination)
107
+ output_formatted_list(
108
+ items,
109
+ format_output,
110
+ headers,
111
+ column_widths,
112
+ formatter_func,
113
+ empty_message,
114
+ f"{item_name}(s)",
115
+ )
116
+ # If the caller provided a server-side total, display a
117
+ # concise summary like the notebook listing after rendering
118
+ # the formatted table for the current page.
119
+ if total_count is not None:
120
+ shown = int(shown_count) if shown_count is not None else len(items)
121
+ remaining = max(int(total_count) - shown, 0)
122
+ click.echo(
123
+ f"\nShowing {shown} of {total_count} {item_name}(s). {remaining} more available."
124
+ )
125
+ else:
126
+ # Fallback to simple JSON/basic formatting
127
+ if format_output.lower() == "json":
128
+ click.echo(json.dumps(items, indent=2))
129
+ else:
130
+ if not items:
131
+ click.echo(empty_message)
132
+ else:
133
+ for item in items:
134
+ click.echo(f"• {item.get('name', item.get('id', 'Unknown'))}")
135
+
136
+ # If the caller provided a server-side total, display a
137
+ # concise summary like the notebook listing:
138
+ # "Showing 25 of 556 webapp(s). 531 more available."
139
+ if total_count is not None:
140
+ # If shown_count wasn't provided, assume current page length
141
+ shown = int(shown_count) if shown_count is not None else len(items)
142
+ remaining = max(int(total_count) - shown, 0)
143
+ click.echo(
144
+ f"\nShowing {shown} of {total_count} {item_name}(s). {remaining} more available."
145
+ )
146
+ else:
147
+ # Fallback to the old behaviour
148
+ click.echo(f"\nTotal: {len(items)} {item_name}(s)")
149
+
150
+ except Exception as exc:
151
+ handle_api_error(exc)
152
+
153
+ @staticmethod
154
+ def handle_get_response(
155
+ resp: requests.Response,
156
+ item_name: str,
157
+ format_output: str,
158
+ table_formatter_func: Optional[Callable[[Dict[str, Any]], List[str]]] = None,
159
+ ) -> None:
160
+ """Handle get response with standardized formatting."""
161
+ try:
162
+ data = resp.json()
163
+
164
+ if format_output.lower() == "json":
165
+ click.echo(json.dumps(data, indent=2))
166
+ else:
167
+ if table_formatter_func:
168
+ table_formatter_func(data)
169
+ else:
170
+ # Fallback to basic info display
171
+ click.echo(f"{item_name.title()} Details")
172
+ click.echo("=" * 50)
173
+ for key, value in data.items():
174
+ if isinstance(value, (str, int, bool, float)):
175
+ click.echo(f"{key.title()}: {value}")
176
+
177
+ except Exception as exc:
178
+ handle_api_error(exc)
179
+
180
+ @staticmethod
181
+ def handle_create_response(
182
+ resp: requests.Response,
183
+ item_name: str,
184
+ success_message_template: str = "✓ {item_name} created successfully.",
185
+ ) -> None:
186
+ """Handle create response with standardized success messaging."""
187
+ try:
188
+ if resp.status_code in (200, 201):
189
+ data = resp.json() if resp.text.strip() else {}
190
+ message = success_message_template.format(item_name=item_name.title())
191
+
192
+ format_success(message, data)
193
+ else:
194
+ click.echo(f"✗ Failed to create {item_name}", err=True)
195
+ sys.exit(ExitCodes.GENERAL_ERROR)
196
+
197
+ except Exception as exc:
198
+ handle_api_error(exc)
199
+
200
+ @staticmethod
201
+ def handle_update_response(
202
+ resp: requests.Response,
203
+ item_name: str,
204
+ success_message_template: str = "✓ {item_name} updated successfully.",
205
+ ) -> None:
206
+ """Handle update response with standardized success messaging."""
207
+ try:
208
+ if resp.status_code in (200, 204):
209
+ data = resp.json() if resp.text.strip() else {}
210
+ message = success_message_template.format(item_name=item_name.title())
211
+
212
+ format_success(message, data)
213
+ else:
214
+ click.echo(f"✗ Failed to update {item_name}", err=True)
215
+ sys.exit(ExitCodes.GENERAL_ERROR)
216
+
217
+ except Exception as exc:
218
+ handle_api_error(exc)
219
+
220
+ @staticmethod
221
+ def handle_delete_response(
222
+ resp: requests.Response,
223
+ item_name: str,
224
+ item_count: int = 1,
225
+ success_message_template: str = "✓ {count} {item_name}(s) deleted successfully.",
226
+ ) -> None:
227
+ """Handle delete response with standardized success messaging."""
228
+ try:
229
+ if resp.status_code in (200, 204):
230
+ message = success_message_template.format(count=item_count, item_name=item_name)
231
+ click.echo(message)
232
+ else:
233
+ click.echo(f"✗ Failed to delete {item_name}(s)", err=True)
234
+ sys.exit(ExitCodes.GENERAL_ERROR)
235
+
236
+ except Exception as exc:
237
+ handle_api_error(exc)
238
+
239
+ @staticmethod
240
+ def handle_export_response(
241
+ resp: requests.Response,
242
+ item_name: str,
243
+ output_file: str,
244
+ success_message_template: str = "✓ {item_name} exported to {output_file}",
245
+ ) -> None:
246
+ """Handle export response with file saving."""
247
+ from .utils import save_json_file
248
+
249
+ try:
250
+ data = resp.json()
251
+ save_json_file(data, output_file)
252
+
253
+ message = success_message_template.format(
254
+ item_name=item_name.title(), output_file=output_file
255
+ )
256
+ click.echo(message)
257
+
258
+ except Exception as exc:
259
+ handle_api_error(exc)
260
+
261
+
262
+ class BatchResponseHandler:
263
+ """Handler for batch operations with progress tracking."""
264
+
265
+ @staticmethod
266
+ def handle_batch_operation(
267
+ items: List[Dict[str, Any]],
268
+ operation_func: Callable[[Dict[str, Any]], None],
269
+ operation_name: str,
270
+ item_name: str,
271
+ show_progress: bool = True,
272
+ ) -> None:
273
+ """Handle batch operations with progress tracking."""
274
+ total = len(items)
275
+ success_count = 0
276
+ failed_items = []
277
+
278
+ for i, item in enumerate(items):
279
+ if show_progress and total > 1:
280
+ click.echo(f"Processing {operation_name} {i + 1}/{total}...", nl=False)
281
+
282
+ try:
283
+ operation_func(item)
284
+ success_count += 1
285
+ if show_progress and total > 1:
286
+ click.echo(" ✓")
287
+ except Exception as exc:
288
+ failed_items.append({"item": item, "error": str(exc)})
289
+ if show_progress and total > 1:
290
+ click.echo(f" ✗ ({exc})")
291
+
292
+ # Summary
293
+ if success_count > 0:
294
+ click.echo(f"✓ {success_count} {item_name}(s) {operation_name}d successfully.")
295
+
296
+ if failed_items:
297
+ click.echo(f"✗ {len(failed_items)} {item_name}(s) failed:", err=True)
298
+ for failed in failed_items:
299
+ item_id = failed["item"].get("id", failed["item"].get("name", "Unknown")) # type: ignore
300
+ click.echo(f" - {item_id}: {failed['error']}", err=True)
301
+
302
+ if failed_items and success_count == 0:
303
+ sys.exit(ExitCodes.GENERAL_ERROR)
304
+ elif failed_items:
305
+ sys.exit(ExitCodes.INVALID_INPUT)