systemlink-cli 1.13.10__tar.gz → 1.13.12__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.
Files changed (93) hide show
  1. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/PKG-INFO +2 -2
  2. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/pyproject.toml +2 -2
  3. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/_version.py +1 -1
  4. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/main.py +195 -12
  5. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/LICENSE +0 -0
  6. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/dff-editor/editor.js +0 -0
  7. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/dff-editor/index.html +0 -0
  8. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/__init__.py +0 -0
  9. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/__main__.py +0 -0
  10. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/asset_click.py +0 -0
  11. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/cli_formatters.py +0 -0
  12. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/cli_utils.py +0 -0
  13. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/comment_click.py +0 -0
  14. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/completion_click.py +0 -0
  15. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/config.py +0 -0
  16. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/config_click.py +0 -0
  17. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/dataframe_click.py +0 -0
  18. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/dff_click.py +0 -0
  19. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/dff_decorators.py +0 -0
  20. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/example_click.py +0 -0
  21. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/example_loader.py +0 -0
  22. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/example_provisioner.py +0 -0
  23. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/README.md +0 -0
  24. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/_schema/schema-v1.0.json +0 -0
  25. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/demo-complete-workflow/README.md +0 -0
  26. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
  27. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/demo-test-plans/README.md +0 -0
  28. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/demo-test-plans/config.yaml +0 -0
  29. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
  30. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
  31. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
  32. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
  33. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
  34. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
  35. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
  36. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
  37. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
  38. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  39. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/feed_click.py +0 -0
  40. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/file_click.py +0 -0
  41. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/function_click.py +0 -0
  42. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/function_templates.py +0 -0
  43. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/mcp_click.py +0 -0
  44. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/mcp_reachability.py +0 -0
  45. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/mcp_server.py +0 -0
  46. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/notebook_click.py +0 -0
  47. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/platform.py +0 -0
  48. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/policy_click.py +0 -0
  49. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/policy_utils.py +0 -0
  50. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/profiles.py +0 -0
  51. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/response_handlers.py +0 -0
  52. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/rich_output.py +0 -0
  53. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/routine_click.py +0 -0
  54. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skill_click.py +0 -0
  55. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/nipkg-file-package/SKILL.md +0 -0
  56. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/slcli/SKILL.md +0 -0
  57. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
  58. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/slcli/references/commands.md +0 -0
  59. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
  60. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/slcli/references/filtering.md +0 -0
  61. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/slcli/references/routine-examples.md +0 -0
  62. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/slcli/references/troubleshooting.md +0 -0
  63. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-job-debugging/SKILL.md +0 -0
  64. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-notebook/SKILL.md +0 -0
  65. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-notebook/references/interfaces.md +0 -0
  66. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-notebook/references/notebook-patterns.md +0 -0
  67. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-python-test/SKILL.md +0 -0
  68. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-webapp/SKILL.md +0 -0
  69. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-webapp/references/angular-ui-packages.md +0 -0
  70. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
  71. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-webapp/references/layout-patterns.md +0 -0
  72. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
  73. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
  74. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/skills/systemlink-webapp/references/troubleshooting.md +0 -0
  75. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/spec_click.py +0 -0
  76. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/ssl_trust.py +0 -0
  77. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/state_click.py +0 -0
  78. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/system_click.py +0 -0
  79. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/system_query_utils.py +0 -0
  80. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/table_utils.py +0 -0
  81. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/tag_click.py +0 -0
  82. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/templates_click.py +0 -0
  83. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/testmonitor_click.py +0 -0
  84. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/universal_handlers.py +0 -0
  85. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/user_click.py +0 -0
  86. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/utils.py +0 -0
  87. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/web_editor.py +0 -0
  88. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/webapp_click.py +0 -0
  89. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/workflow_preview.py +0 -0
  90. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/workflows_click.py +0 -0
  91. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/workitem_click.py +0 -0
  92. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/workspace_click.py +0 -0
  93. {systemlink_cli-1.13.10 → systemlink_cli-1.13.12}/slcli/workspace_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: systemlink-cli
3
- Version: 1.13.10
3
+ Version: 1.13.12
4
4
  Summary: SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates.
5
5
  License-File: LICENSE
6
6
  Author: Fred Visser
@@ -18,7 +18,7 @@ Requires-Dist: pygments (>=2.20.0)
18
18
  Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
19
19
  Requires-Dist: questionary (>=2.1.1,<3.0.0)
20
20
  Requires-Dist: requests (>=2.32.4,<3.0.0)
21
- Requires-Dist: rich (>=13.7,<15)
21
+ Requires-Dist: rich (>=15,<16)
22
22
  Requires-Dist: rich-click (>=1.8,<2)
23
23
  Requires-Dist: tabulate (>=0.10.0,<0.11.0)
24
24
  Requires-Dist: truststore (>=0.9,<0.11)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "systemlink-cli"
3
- version = "1.13.10"
3
+ version = "1.13.12"
4
4
  description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
5
5
  authors = ["Fred Visser <fred.visser@emerson.com>"]
6
6
  packages = [{ include = "slcli" }]
@@ -38,7 +38,7 @@ watchdog = "^6.0.0"
38
38
  pyyaml = "^6.0.3"
39
39
  questionary = "^2.1.1"
40
40
  packaging = ">=21.0"
41
- rich = ">=13.7,<15"
41
+ rich = ">=15,<16"
42
42
  rich-click = ">=1.8,<2"
43
43
  cryptography = ">=46.0.7"
44
44
  pygments = ">=2.20.0"
@@ -1,4 +1,4 @@
1
1
  """Version information for slcli."""
2
2
 
3
3
  # This file is auto-generated. Do not edit manually.
4
- __version__ = "1.13.10"
4
+ __version__ = "1.13.12"
@@ -1,10 +1,15 @@
1
1
  """slcli entry points."""
2
2
 
3
+ import hashlib
3
4
  import json
5
+ import os
6
+ import socket
7
+ import ssl
4
8
  import tomllib
5
9
  from pathlib import Path
6
10
  from types import ModuleType
7
- from typing import Optional
11
+ from typing import List, Optional, Tuple
12
+ from urllib.parse import urlparse
8
13
 
9
14
  import click as base_click
10
15
  import keyring
@@ -95,6 +100,191 @@ def _configure_rich_click_command_groups() -> None:
95
100
  }
96
101
 
97
102
 
103
+ def _get_ca_source_display() -> str:
104
+ """Describe the CA source used for HTTPS verification."""
105
+ if OS_TRUST_INJECTED:
106
+ return f"system (reason={OS_TRUST_REASON})"
107
+
108
+ verify_env = os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE")
109
+ if verify_env:
110
+ return f"custom-pem ({verify_env})"
111
+
112
+ return f"certifi (reason={OS_TRUST_REASON})"
113
+
114
+
115
+ def _build_tls_debug_context(ssl_verify: bool) -> ssl.SSLContext:
116
+ """Build an SSL context aligned with the current CLI trust configuration."""
117
+ if not ssl_verify:
118
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
119
+ ctx.check_hostname = False
120
+ ctx.verify_mode = ssl.CERT_NONE
121
+ return ctx
122
+
123
+ verify_env = os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE")
124
+ if verify_env:
125
+ return ssl.create_default_context(cafile=verify_env)
126
+
127
+ if OS_TRUST_INJECTED:
128
+ try:
129
+ import truststore # type: ignore[import-not-found]
130
+
131
+ return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
132
+ except (AttributeError, ImportError, RuntimeError, OSError, ssl.SSLError, ValueError):
133
+ pass
134
+
135
+ return ssl.create_default_context()
136
+
137
+
138
+ def _format_cert_name(name: object) -> str:
139
+ """Flatten SSL certificate subject or issuer names for display."""
140
+ if not isinstance(name, tuple):
141
+ return "Unavailable"
142
+
143
+ parts: List[str] = []
144
+ for relative_name in name:
145
+ if not isinstance(relative_name, tuple):
146
+ continue
147
+ try:
148
+ for key, value in relative_name:
149
+ if not isinstance(key, str) or not isinstance(value, str):
150
+ continue
151
+ parts.append(f"{key}={value}")
152
+ except (TypeError, ValueError):
153
+ continue
154
+ return ", ".join(parts) if parts else "Unavailable"
155
+
156
+
157
+ def _format_subject_alt_names(subject_alt_names: object) -> str:
158
+ """Format certificate SANs for compact debug output."""
159
+ if not isinstance(subject_alt_names, tuple) or not subject_alt_names:
160
+ return "Unavailable"
161
+
162
+ entries: List[str] = []
163
+ extra_count = 0
164
+ for subject_alt_name in subject_alt_names:
165
+ if (
166
+ not isinstance(subject_alt_name, tuple)
167
+ or len(subject_alt_name) != 2
168
+ or not isinstance(subject_alt_name[0], str)
169
+ or not isinstance(subject_alt_name[1], str)
170
+ ):
171
+ continue
172
+
173
+ if len(entries) < 5:
174
+ entries.append(f"{subject_alt_name[0]}={subject_alt_name[1]}")
175
+ else:
176
+ extra_count += 1
177
+
178
+ if extra_count:
179
+ entries.append(f"... (+{extra_count} more)")
180
+
181
+ return ", ".join(entries)
182
+
183
+
184
+ def _get_proxy_debug_rows(api_url: str) -> List[Tuple[str, str]]:
185
+ """Return a compact view of proxy environment state for the target URL."""
186
+ import requests
187
+
188
+ effective_proxies = requests.utils.get_environ_proxies(api_url)
189
+ return [
190
+ (
191
+ "Proxy HTTPS",
192
+ "set" if os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy") else "unset",
193
+ ),
194
+ (
195
+ "Proxy HTTP",
196
+ "set" if os.environ.get("HTTP_PROXY") or os.environ.get("http_proxy") else "unset",
197
+ ),
198
+ (
199
+ "Proxy NO_PROXY",
200
+ "set" if os.environ.get("NO_PROXY") or os.environ.get("no_proxy") else "unset",
201
+ ),
202
+ ("Proxy Active", "yes" if effective_proxies else "no"),
203
+ ]
204
+
205
+
206
+ def _probe_tls_connection(api_url: str, ssl_verify: bool) -> List[Tuple[str, str]]:
207
+ """Collect TLS handshake and leaf certificate details for the API host."""
208
+ import requests
209
+
210
+ if not api_url or api_url == "Not configured":
211
+ return [("TLS Probe", "Skipped (API URL not configured)")]
212
+
213
+ parsed = urlparse(api_url)
214
+ if parsed.scheme.lower() != "https":
215
+ return [("TLS Probe", "Skipped (non-HTTPS API URL)")]
216
+
217
+ hostname = parsed.hostname
218
+ if not hostname:
219
+ return [("TLS Probe", "Skipped (unable to parse host)")]
220
+
221
+ if requests.utils.get_environ_proxies(api_url):
222
+ return [("TLS Probe", "Skipped (proxy-configured environment)")]
223
+
224
+ port = parsed.port or 443
225
+ target = f"{hostname}:{port}"
226
+
227
+ try:
228
+ context = _build_tls_debug_context(ssl_verify)
229
+ with socket.create_connection((hostname, port), timeout=5) as tcp_socket:
230
+ with context.wrap_socket(tcp_socket, server_hostname=hostname) as tls_socket:
231
+ cert = tls_socket.getpeercert()
232
+ cert_bytes = tls_socket.getpeercert(binary_form=True)
233
+ cipher = tls_socket.cipher()
234
+
235
+ rows: List[Tuple[str, str]] = [
236
+ ("TLS Target", target),
237
+ ("TLS Version", tls_socket.version() or "Unavailable"),
238
+ ("TLS Cipher", cipher[0] if cipher else "Unavailable"),
239
+ ]
240
+
241
+ if isinstance(cert, dict):
242
+ rows.extend(
243
+ [
244
+ ("Leaf Cert Subject", _format_cert_name(cert.get("subject", ()))),
245
+ ("Leaf Cert Issuer", _format_cert_name(cert.get("issuer", ()))),
246
+ (
247
+ "Leaf Cert SANs",
248
+ _format_subject_alt_names(cert.get("subjectAltName", ())),
249
+ ),
250
+ ("Leaf Cert Valid From", str(cert.get("notBefore", "Unavailable"))),
251
+ ("Leaf Cert Valid To", str(cert.get("notAfter", "Unavailable"))),
252
+ ]
253
+ )
254
+
255
+ if cert_bytes:
256
+ rows.append(("Leaf Cert SHA256", hashlib.sha256(cert_bytes).hexdigest()))
257
+
258
+ return rows
259
+ except Exception as exc:
260
+ return [
261
+ ("TLS Target", target),
262
+ ("TLS Probe Error", f"{exc.__class__.__name__}: {exc}"),
263
+ ]
264
+
265
+
266
+ def _collect_info_debug_rows(api_url: str) -> List[Tuple[str, str]]:
267
+ """Return structured connection diagnostics for info --debug."""
268
+ from .utils import get_ssl_verify
269
+
270
+ ssl_verify = get_ssl_verify()
271
+ rows = [
272
+ ("SSL Verify", "enabled" if ssl_verify else "disabled"),
273
+ ("CA Source", _get_ca_source_display()),
274
+ ]
275
+ rows.extend(_get_proxy_debug_rows(api_url))
276
+ rows.extend(_probe_tls_connection(api_url, ssl_verify))
277
+ return rows
278
+
279
+
280
+ def _emit_info_debug_diagnostics(api_url: str) -> None:
281
+ """Write structured connection diagnostics to stderr."""
282
+ click.echo("Debug Connection Diagnostics:", err=True)
283
+ for label, value in _collect_info_debug_rows(api_url):
284
+ click.echo(f" {label}: {value}", err=True)
285
+ click.echo(err=True)
286
+
287
+
98
288
  _configure_rich_click_command_groups()
99
289
 
100
290
 
@@ -201,17 +391,7 @@ def cli(ctx: base_click.Context, version: bool, profile: Optional[str]) -> None:
201
391
  @cli.command(hidden=True, name="_ca-info")
202
392
  def ca_info() -> None:
203
393
  """Show TLS CA trust source (hidden diagnostic)."""
204
- if OS_TRUST_INJECTED:
205
- click.echo(f"CA Source: system (reason={OS_TRUST_REASON})")
206
- else:
207
- # Determine if custom verify path set via env
208
- import os
209
-
210
- verify_env = os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE")
211
- if verify_env:
212
- click.echo(f"CA Source: custom-pem ({verify_env})")
213
- else:
214
- click.echo(f"CA Source: certifi (reason={OS_TRUST_REASON})")
394
+ click.echo(f"CA Source: {_get_ca_source_display()}")
215
395
 
216
396
 
217
397
  @cli.command()
@@ -375,6 +555,9 @@ def info(format: str, skip_health: bool, debug: bool) -> None:
375
555
 
376
556
  platform_info = get_platform_info(skip_health=skip_health)
377
557
 
558
+ if debug:
559
+ _emit_info_debug_diagnostics(platform_info.get("api_url") or "Not configured")
560
+
378
561
  # Add profile information
379
562
  cfg = ProfileConfig.load()
380
563
  active_profile = get_active_profile()