systemlink-cli 1.13.14__tar.gz → 1.13.16__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.
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/PKG-INFO +1 -1
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/pyproject.toml +1 -1
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/_version.py +1 -1
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/config_click.py +61 -2
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/main.py +18 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/platform.py +42 -19
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/utils.py +143 -73
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/LICENSE +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/dff-editor/editor.js +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/dff-editor/index.html +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/__init__.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/__main__.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/comment_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/completion_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/config.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/dataframe_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/dff_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/example_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/example_loader.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/example_provisioner.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/README.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/_schema/schema-v1.0.json +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/demo-complete-workflow/README.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/demo-test-plans/README.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/demo-test-plans/config.yaml +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/feed_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/file_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/function_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/mcp_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/mcp_reachability.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/mcp_server.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/notebook_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/policy_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/profiles.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/rich_output.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/routine_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skill_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/nipkg-file-package/SKILL.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/SKILL.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/commands.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/routine-examples.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/troubleshooting.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-job-debugging/SKILL.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-notebook/SKILL.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-notebook/references/interfaces.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-notebook/references/notebook-patterns.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-python-test/SKILL.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-webapp/SKILL.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-webapp/references/angular-ui-packages.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-webapp/references/layout-patterns.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-webapp/references/troubleshooting.md +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/spec_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/ssl_trust.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/state_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/system_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/system_query_utils.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/tag_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/templates_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/testmonitor_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/user_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/web_editor.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/webapp_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/workitem_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/workspace_click.py +0 -0
- {systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/workspace_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "systemlink-cli"
|
|
3
|
-
version = "1.13.
|
|
3
|
+
version = "1.13.16"
|
|
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" }]
|
|
@@ -18,7 +18,14 @@ from .platform import (
|
|
|
18
18
|
from .profiles import ProfileConfig, Profile, check_config_file_permissions
|
|
19
19
|
from .rich_output import render_table
|
|
20
20
|
from .table_utils import output_formatted_list
|
|
21
|
-
from .utils import
|
|
21
|
+
from .utils import (
|
|
22
|
+
ExitCodes,
|
|
23
|
+
describe_config_source,
|
|
24
|
+
get_api_key_resolution,
|
|
25
|
+
get_base_url_resolution,
|
|
26
|
+
get_web_url_resolution,
|
|
27
|
+
source_is_env,
|
|
28
|
+
)
|
|
22
29
|
|
|
23
30
|
API_KEY_LENGTH = 42
|
|
24
31
|
API_KEY_PATTERN = re.compile(rf"^[A-Za-z0-9_-]{{{API_KEY_LENGTH}}}$")
|
|
@@ -372,6 +379,38 @@ def register_config_commands(cli: Any) -> None:
|
|
|
372
379
|
"""View the full configuration."""
|
|
373
380
|
cfg = ProfileConfig.load()
|
|
374
381
|
|
|
382
|
+
effective: dict[str, Any] = {}
|
|
383
|
+
env_overrides: list[str] = []
|
|
384
|
+
|
|
385
|
+
try:
|
|
386
|
+
api_url_resolution = get_base_url_resolution()
|
|
387
|
+
effective["api-url"] = api_url_resolution.value
|
|
388
|
+
effective["api-url-source"] = api_url_resolution.source
|
|
389
|
+
if source_is_env(api_url_resolution.source):
|
|
390
|
+
env_overrides.append("API URL")
|
|
391
|
+
except Exception:
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
api_key_resolution = get_api_key_resolution(emit_error=False)
|
|
396
|
+
effective["api-key-source"] = api_key_resolution.source
|
|
397
|
+
if source_is_env(api_key_resolution.source):
|
|
398
|
+
env_overrides.append("API Key")
|
|
399
|
+
except Exception:
|
|
400
|
+
pass
|
|
401
|
+
|
|
402
|
+
try:
|
|
403
|
+
web_url_resolution = get_web_url_resolution()
|
|
404
|
+
effective["web-url"] = web_url_resolution.value
|
|
405
|
+
effective["web-url-source"] = web_url_resolution.source
|
|
406
|
+
if source_is_env(web_url_resolution.source):
|
|
407
|
+
env_overrides.append("Web URL")
|
|
408
|
+
except Exception:
|
|
409
|
+
pass
|
|
410
|
+
|
|
411
|
+
if env_overrides:
|
|
412
|
+
effective["env-overrides"] = env_overrides
|
|
413
|
+
|
|
375
414
|
if format == "json":
|
|
376
415
|
data: dict = {}
|
|
377
416
|
if cfg.current_profile:
|
|
@@ -388,6 +427,8 @@ def register_config_commands(cli: Any) -> None:
|
|
|
388
427
|
data["profiles"][name] = profile_dict
|
|
389
428
|
if cfg.settings:
|
|
390
429
|
data.update(cfg.settings)
|
|
430
|
+
if effective:
|
|
431
|
+
data["effective"] = effective
|
|
391
432
|
click.echo(json.dumps(data, indent=2))
|
|
392
433
|
return
|
|
393
434
|
|
|
@@ -412,7 +453,7 @@ def register_config_commands(cli: Any) -> None:
|
|
|
412
453
|
api_key_display = (
|
|
413
454
|
"****" + profile.api_key[-4:] if len(profile.api_key) >= 4 else "****"
|
|
414
455
|
)
|
|
415
|
-
rows.append(["API Key", api_key_display])
|
|
456
|
+
rows.append(["Profile API Key", api_key_display])
|
|
416
457
|
|
|
417
458
|
if profile.workspace:
|
|
418
459
|
rows.append(["Workspace", profile.workspace])
|
|
@@ -420,6 +461,24 @@ def register_config_commands(cli: Any) -> None:
|
|
|
420
461
|
if profile.readonly:
|
|
421
462
|
rows.append(["Readonly", "enabled"])
|
|
422
463
|
|
|
464
|
+
if effective:
|
|
465
|
+
api_url_source = effective.get("api-url-source")
|
|
466
|
+
if isinstance(api_url_source, str):
|
|
467
|
+
rows.append(["Effective API URL", str(effective.get("api-url", ""))])
|
|
468
|
+
rows.append(["API URL Source", describe_config_source(api_url_source)])
|
|
469
|
+
|
|
470
|
+
api_key_source = effective.get("api-key-source")
|
|
471
|
+
if isinstance(api_key_source, str):
|
|
472
|
+
rows.append(["API Key Source", describe_config_source(api_key_source)])
|
|
473
|
+
|
|
474
|
+
web_url_source = effective.get("web-url-source")
|
|
475
|
+
if isinstance(web_url_source, str):
|
|
476
|
+
rows.append(["Effective Web URL", str(effective.get("web-url", ""))])
|
|
477
|
+
rows.append(["Web URL Source", describe_config_source(web_url_source)])
|
|
478
|
+
|
|
479
|
+
if env_overrides:
|
|
480
|
+
rows.append(["Active Overrides", ", ".join(env_overrides)])
|
|
481
|
+
|
|
423
482
|
click.echo("slcli Configuration:")
|
|
424
483
|
render_table(
|
|
425
484
|
headers=["SETTING", "VALUE"],
|
|
@@ -41,6 +41,7 @@ from .tag_click import register_tag_commands
|
|
|
41
41
|
from .templates_click import register_templates_commands
|
|
42
42
|
from .testmonitor_click import register_testmonitor_commands
|
|
43
43
|
from .user_click import register_user_commands
|
|
44
|
+
from .utils import describe_config_source
|
|
44
45
|
from .webapp_click import register_webapp_commands
|
|
45
46
|
from .workitem_click import register_workitem_commands
|
|
46
47
|
from .workspace_click import register_workspace_commands
|
|
@@ -596,14 +597,31 @@ def info(format: str, skip_health: bool, debug: bool) -> None:
|
|
|
596
597
|
api_url = truncate(platform_info.get("api_url", "Not configured"))
|
|
597
598
|
web_url = truncate(platform_info.get("web_url", "Not configured"))
|
|
598
599
|
|
|
600
|
+
api_url_source = truncate(
|
|
601
|
+
describe_config_source(platform_info.get("api_url_source", "Unknown"))
|
|
602
|
+
)
|
|
603
|
+
api_key_source = truncate(
|
|
604
|
+
describe_config_source(platform_info.get("api_key_source", "Unknown"))
|
|
605
|
+
)
|
|
606
|
+
web_url_source = truncate(
|
|
607
|
+
describe_config_source(platform_info.get("web_url_source", "Unknown"))
|
|
608
|
+
)
|
|
609
|
+
|
|
599
610
|
info_rows = [
|
|
600
611
|
["Status", status],
|
|
601
612
|
["Profile", profile_display],
|
|
602
613
|
["Platform", platform_display],
|
|
603
614
|
["API URL", api_url],
|
|
615
|
+
["API URL Source", api_url_source],
|
|
616
|
+
["API Key Source", api_key_source],
|
|
604
617
|
["Web URL", web_url],
|
|
618
|
+
["Web URL Source", web_url_source],
|
|
605
619
|
]
|
|
606
620
|
|
|
621
|
+
env_overrides = platform_info.get("env_overrides")
|
|
622
|
+
if isinstance(env_overrides, list) and env_overrides:
|
|
623
|
+
info_rows.append(["Overrides", truncate(", ".join(str(item) for item in env_overrides))])
|
|
624
|
+
|
|
607
625
|
workspace = platform_info.get("active_profile_workspace")
|
|
608
626
|
if workspace:
|
|
609
627
|
workspace_display = truncate(workspace)
|
|
@@ -249,22 +249,19 @@ def _get_cached_service_status(service_name: str, api_url: str, api_key: str) ->
|
|
|
249
249
|
|
|
250
250
|
def _get_current_api_context() -> Optional[Tuple[Optional[str], str, str]]:
|
|
251
251
|
"""Resolve the current profile name, API URL, and key for service probing."""
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if api_url and api_key:
|
|
255
|
-
return None, api_url, api_key
|
|
252
|
+
try:
|
|
253
|
+
from .utils import get_api_key_resolution, get_base_url_resolution
|
|
256
254
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
config_api_key = cfg.get("api_key") if cfg else None
|
|
260
|
-
if config_api_url and config_api_key:
|
|
261
|
-
return None, str(config_api_url), str(config_api_key)
|
|
255
|
+
api_url_resolution = get_base_url_resolution()
|
|
256
|
+
api_key_resolution = get_api_key_resolution(emit_error=False)
|
|
262
257
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
profile_name: Optional[str] = None
|
|
259
|
+
if api_url_resolution.source.startswith("profile:") and (
|
|
260
|
+
api_url_resolution.source == api_key_resolution.source
|
|
261
|
+
):
|
|
262
|
+
profile_name = api_url_resolution.source.split(":", 1)[1]
|
|
266
263
|
|
|
267
|
-
return
|
|
264
|
+
return profile_name, api_url_resolution.value, api_key_resolution.value
|
|
268
265
|
except Exception:
|
|
269
266
|
return None
|
|
270
267
|
|
|
@@ -376,7 +373,7 @@ def _get_runtime_platform() -> str:
|
|
|
376
373
|
if platform != PLATFORM_UNKNOWN:
|
|
377
374
|
return platform
|
|
378
375
|
|
|
379
|
-
status_snapshot = _get_service_status_snapshot(force_refresh=
|
|
376
|
+
status_snapshot = _get_service_status_snapshot(force_refresh=True)
|
|
380
377
|
if status_snapshot is None:
|
|
381
378
|
return platform
|
|
382
379
|
|
|
@@ -779,23 +776,36 @@ def get_platform_info(skip_health: bool = False) -> Dict[str, Any]:
|
|
|
779
776
|
Returns:
|
|
780
777
|
Dictionary with platform info including URL, platform type, and services.
|
|
781
778
|
"""
|
|
782
|
-
from .utils import
|
|
779
|
+
from .utils import (
|
|
780
|
+
get_api_key_resolution,
|
|
781
|
+
get_base_url_resolution,
|
|
782
|
+
get_web_url_resolution,
|
|
783
|
+
source_is_env,
|
|
784
|
+
)
|
|
783
785
|
|
|
784
|
-
# Use profile-aware functions instead of keyring directly
|
|
785
786
|
try:
|
|
786
|
-
|
|
787
|
+
api_url_resolution = get_base_url_resolution()
|
|
788
|
+
api_url = api_url_resolution.value
|
|
789
|
+
api_url_source = api_url_resolution.source
|
|
787
790
|
except Exception:
|
|
788
791
|
api_url = "Not configured"
|
|
792
|
+
api_url_source = "unresolved"
|
|
789
793
|
|
|
790
794
|
try:
|
|
791
|
-
|
|
795
|
+
web_url_resolution = get_web_url_resolution()
|
|
796
|
+
web_url = web_url_resolution.value
|
|
797
|
+
web_url_source = web_url_resolution.source
|
|
792
798
|
except Exception:
|
|
793
799
|
web_url = "Not configured"
|
|
800
|
+
web_url_source = "unresolved"
|
|
794
801
|
|
|
795
802
|
try:
|
|
796
|
-
|
|
803
|
+
api_key_resolution = get_api_key_resolution(emit_error=False)
|
|
804
|
+
api_key = api_key_resolution.value
|
|
805
|
+
api_key_source = api_key_resolution.source
|
|
797
806
|
logged_in = bool(api_key)
|
|
798
807
|
except Exception:
|
|
808
|
+
api_key_source = "unresolved"
|
|
799
809
|
logged_in = False
|
|
800
810
|
|
|
801
811
|
# Get platform from profile or keyring config
|
|
@@ -833,7 +843,10 @@ def get_platform_info(skip_health: bool = False) -> Dict[str, Any]:
|
|
|
833
843
|
|
|
834
844
|
info: Dict[str, Any] = {
|
|
835
845
|
"api_url": api_url,
|
|
846
|
+
"api_url_source": api_url_source,
|
|
836
847
|
"web_url": web_url,
|
|
848
|
+
"web_url_source": web_url_source,
|
|
849
|
+
"api_key_source": api_key_source,
|
|
837
850
|
"platform": platform,
|
|
838
851
|
"platform_display": _get_platform_display_name(platform),
|
|
839
852
|
"logged_in": logged_in,
|
|
@@ -841,6 +854,16 @@ def get_platform_info(skip_health: bool = False) -> Dict[str, Any]:
|
|
|
841
854
|
"auth_valid": auth_valid,
|
|
842
855
|
}
|
|
843
856
|
|
|
857
|
+
env_overrides = []
|
|
858
|
+
if source_is_env(api_url_source):
|
|
859
|
+
env_overrides.append("API URL")
|
|
860
|
+
if source_is_env(api_key_source):
|
|
861
|
+
env_overrides.append("API Key")
|
|
862
|
+
if source_is_env(web_url_source):
|
|
863
|
+
env_overrides.append("Web URL")
|
|
864
|
+
if env_overrides:
|
|
865
|
+
info["env_overrides"] = env_overrides
|
|
866
|
+
|
|
844
867
|
if file_query_endpoint is not None:
|
|
845
868
|
info["file_query_endpoint"] = file_query_endpoint
|
|
846
869
|
if elasticsearch_available is not None:
|
|
@@ -4,7 +4,8 @@ import datetime
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
-
from
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Dict, List, Any, Optional, Callable, Sequence
|
|
8
9
|
|
|
9
10
|
import click
|
|
10
11
|
import keyring
|
|
@@ -29,6 +30,14 @@ class SystemLinkConfig:
|
|
|
29
30
|
self.ssl_verify = ssl_verify
|
|
30
31
|
|
|
31
32
|
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ResolvedConfigValue:
|
|
35
|
+
"""Resolved configuration value plus the source that supplied it."""
|
|
36
|
+
|
|
37
|
+
value: str
|
|
38
|
+
source: str
|
|
39
|
+
|
|
40
|
+
|
|
32
41
|
class ExitCodes:
|
|
33
42
|
"""Standard exit codes for CLI operations."""
|
|
34
43
|
|
|
@@ -309,100 +318,195 @@ def get_http_configuration() -> SystemLinkConfig:
|
|
|
309
318
|
)
|
|
310
319
|
|
|
311
320
|
|
|
312
|
-
def
|
|
313
|
-
"""
|
|
321
|
+
def _get_env_override(env_names: Sequence[str]) -> Optional[ResolvedConfigValue]:
|
|
322
|
+
"""Return the first non-empty environment override from the provided names."""
|
|
323
|
+
for env_name in env_names:
|
|
324
|
+
value = os.environ.get(env_name)
|
|
325
|
+
if value:
|
|
326
|
+
return ResolvedConfigValue(value=value, source=f"env:{env_name}")
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def source_is_env(source: str) -> bool:
|
|
331
|
+
"""Return True when the resolved source came from an environment variable."""
|
|
332
|
+
return source.startswith("env:")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def describe_config_source(source: str) -> str:
|
|
336
|
+
"""Return a user-facing description of a resolved configuration source."""
|
|
337
|
+
if source.startswith("env:"):
|
|
338
|
+
return f"Environment ({source.split(':', 1)[1]})"
|
|
339
|
+
if source.startswith("profile:"):
|
|
340
|
+
return f"Profile '{source.split(':', 1)[1]}'"
|
|
341
|
+
if source.startswith("keyring:"):
|
|
342
|
+
return f"Legacy keyring ({source.split(':', 1)[1]})"
|
|
343
|
+
if source.startswith("default:"):
|
|
344
|
+
return f"Default ({source.split(':', 1)[1]})"
|
|
345
|
+
if source.startswith("derived:"):
|
|
346
|
+
derived_from = source.split(":", 1)[1]
|
|
347
|
+
return f"Derived from API URL via {describe_config_source(derived_from)}"
|
|
348
|
+
return source
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_base_url_resolution() -> ResolvedConfigValue:
|
|
352
|
+
"""Resolve the SystemLink API base URL and record where it came from.
|
|
314
353
|
|
|
315
354
|
Preference order:
|
|
316
|
-
1. Environment variable
|
|
355
|
+
1. Environment variable SLCLI_API_URL (preferred) or SYSTEMLINK_API_URL
|
|
317
356
|
2. Active profile from config file
|
|
318
357
|
3. Combined keyring config (legacy)
|
|
319
358
|
4. Legacy keyring entry SYSTEMLINK_API_URL
|
|
320
359
|
5. Default fallback to localhost
|
|
321
360
|
"""
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return url
|
|
361
|
+
override = _get_env_override(("SLCLI_API_URL", "SYSTEMLINK_API_URL"))
|
|
362
|
+
if override is not None:
|
|
363
|
+
return ResolvedConfigValue(override.value.rstrip("/"), override.source)
|
|
326
364
|
|
|
327
|
-
# Second, try the active profile
|
|
328
365
|
try:
|
|
329
366
|
from .profiles import get_active_profile
|
|
330
367
|
|
|
331
368
|
profile = get_active_profile()
|
|
332
369
|
if profile and profile.server:
|
|
333
|
-
return profile.server
|
|
370
|
+
return ResolvedConfigValue(profile.server.rstrip("/"), f"profile:{profile.name}")
|
|
334
371
|
except (FileNotFoundError, json.JSONDecodeError, KeyError, AttributeError):
|
|
335
|
-
# Profile file missing, corrupted, or malformed - fall back to keyring
|
|
336
372
|
pass
|
|
337
373
|
|
|
338
|
-
# Third, try the combined keyring config (legacy)
|
|
339
374
|
cfg = _get_keyring_config()
|
|
340
375
|
if cfg and isinstance(cfg, dict):
|
|
341
376
|
config_url = cfg.get("api_url")
|
|
342
377
|
if config_url:
|
|
343
|
-
return config_url
|
|
378
|
+
return ResolvedConfigValue(str(config_url).rstrip("/"), "keyring:SYSTEMLINK_CONFIG")
|
|
344
379
|
|
|
345
|
-
|
|
346
|
-
|
|
380
|
+
try:
|
|
381
|
+
url = keyring.get_password("systemlink-cli", "SYSTEMLINK_API_URL")
|
|
382
|
+
except Exception:
|
|
383
|
+
url = None
|
|
347
384
|
if url:
|
|
348
|
-
return url
|
|
385
|
+
return ResolvedConfigValue(url.rstrip("/"), "keyring:SYSTEMLINK_API_URL")
|
|
349
386
|
|
|
350
|
-
return "http://localhost:8000"
|
|
387
|
+
return ResolvedConfigValue("http://localhost:8000", "default:localhost")
|
|
351
388
|
|
|
352
389
|
|
|
353
|
-
def
|
|
354
|
-
"""
|
|
390
|
+
def get_web_url_resolution() -> ResolvedConfigValue:
|
|
391
|
+
"""Resolve the SystemLink web UI URL and record where it came from.
|
|
355
392
|
|
|
356
393
|
Preference order:
|
|
357
|
-
1. Environment variable SYSTEMLINK_WEB_URL
|
|
394
|
+
1. Environment variable SLCLI_WEB_URL (preferred) or SYSTEMLINK_WEB_URL
|
|
358
395
|
2. Active profile from config file
|
|
359
396
|
3. Combined keyring config (legacy)
|
|
360
397
|
4. Legacy keyring entry SYSTEMLINK_WEB_URL
|
|
361
|
-
5. Derived from
|
|
398
|
+
5. Derived from the effective API base URL
|
|
362
399
|
"""
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return url.rstrip("/")
|
|
400
|
+
override = _get_env_override(("SLCLI_WEB_URL", "SYSTEMLINK_WEB_URL"))
|
|
401
|
+
if override is not None:
|
|
402
|
+
return ResolvedConfigValue(override.value.rstrip("/"), override.source)
|
|
367
403
|
|
|
368
|
-
# 2) Try the active profile
|
|
369
404
|
try:
|
|
370
405
|
from .profiles import get_active_profile
|
|
371
406
|
|
|
372
407
|
profile = get_active_profile()
|
|
373
408
|
if profile and profile.web_url:
|
|
374
|
-
return profile.web_url.rstrip("/")
|
|
409
|
+
return ResolvedConfigValue(profile.web_url.rstrip("/"), f"profile:{profile.name}")
|
|
375
410
|
except (FileNotFoundError, json.JSONDecodeError, KeyError, AttributeError):
|
|
376
|
-
# Profile unavailable or misconfigured, fall back to other sources
|
|
377
411
|
pass
|
|
378
412
|
|
|
379
|
-
# 3) Combined keyring config entry (legacy)
|
|
380
413
|
cfg = _get_keyring_config()
|
|
381
414
|
if cfg and isinstance(cfg, dict):
|
|
382
415
|
maybe = cfg.get("web_url") or cfg.get("webUrl") or cfg.get("web_ui_url")
|
|
383
416
|
if maybe:
|
|
384
|
-
return str(maybe).rstrip("/")
|
|
417
|
+
return ResolvedConfigValue(str(maybe).rstrip("/"), "keyring:SYSTEMLINK_CONFIG")
|
|
385
418
|
|
|
386
|
-
# 4) Legacy keyring entry fallback
|
|
387
419
|
try:
|
|
388
420
|
url = keyring.get_password("systemlink-cli", "SYSTEMLINK_WEB_URL")
|
|
389
421
|
except Exception:
|
|
390
422
|
url = None
|
|
391
423
|
if url:
|
|
392
|
-
return url.rstrip("/")
|
|
424
|
+
return ResolvedConfigValue(url.rstrip("/"), "keyring:SYSTEMLINK_WEB_URL")
|
|
393
425
|
|
|
394
|
-
|
|
395
|
-
base =
|
|
426
|
+
base_resolution = get_base_url_resolution()
|
|
427
|
+
base = base_resolution.value
|
|
396
428
|
try:
|
|
397
429
|
from urllib.parse import urlparse
|
|
398
430
|
|
|
399
431
|
parsed = urlparse(base if base.startswith("http") else "https://" + base)
|
|
400
432
|
host = parsed.netloc or parsed.path
|
|
401
433
|
if not host:
|
|
402
|
-
return "https://localhost"
|
|
403
|
-
return
|
|
434
|
+
return ResolvedConfigValue("https://localhost", f"derived:{base_resolution.source}")
|
|
435
|
+
return ResolvedConfigValue(
|
|
436
|
+
f"https://{host.rstrip('/')}",
|
|
437
|
+
f"derived:{base_resolution.source}",
|
|
438
|
+
)
|
|
404
439
|
except Exception:
|
|
405
|
-
return "https://localhost"
|
|
440
|
+
return ResolvedConfigValue("https://localhost", f"derived:{base_resolution.source}")
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def get_api_key_resolution(emit_error: bool = True) -> ResolvedConfigValue:
|
|
444
|
+
"""Resolve the SystemLink API key and record where it came from.
|
|
445
|
+
|
|
446
|
+
Preference order:
|
|
447
|
+
1. Environment variable SLCLI_API_KEY (preferred) or SYSTEMLINK_API_KEY
|
|
448
|
+
2. Active profile from config file
|
|
449
|
+
3. Combined keyring config (legacy)
|
|
450
|
+
4. Legacy keyring entry SYSTEMLINK_API_KEY
|
|
451
|
+
"""
|
|
452
|
+
override = _get_env_override(("SLCLI_API_KEY", "SYSTEMLINK_API_KEY"))
|
|
453
|
+
if override is not None:
|
|
454
|
+
return override
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
from .profiles import get_active_profile
|
|
458
|
+
|
|
459
|
+
profile = get_active_profile()
|
|
460
|
+
if profile and profile.api_key:
|
|
461
|
+
return ResolvedConfigValue(profile.api_key, f"profile:{profile.name}")
|
|
462
|
+
except (FileNotFoundError, json.JSONDecodeError, KeyError, AttributeError):
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
cfg = _get_keyring_config()
|
|
466
|
+
if cfg and isinstance(cfg, dict):
|
|
467
|
+
maybe = cfg.get("api_key") or cfg.get("apiKey") or cfg.get("apiToken")
|
|
468
|
+
if maybe:
|
|
469
|
+
return ResolvedConfigValue(str(maybe), "keyring:SYSTEMLINK_CONFIG")
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
api_key = keyring.get_password("systemlink-cli", "SYSTEMLINK_API_KEY")
|
|
473
|
+
except Exception:
|
|
474
|
+
api_key = None
|
|
475
|
+
if api_key:
|
|
476
|
+
return ResolvedConfigValue(api_key, "keyring:SYSTEMLINK_API_KEY")
|
|
477
|
+
|
|
478
|
+
if emit_error:
|
|
479
|
+
raise click.ClickException(
|
|
480
|
+
"API key not found. Please set the SLCLI_API_KEY environment variable "
|
|
481
|
+
"(or legacy SYSTEMLINK_API_KEY) or run 'slcli login --profile <name>'."
|
|
482
|
+
)
|
|
483
|
+
raise click.ClickException("API key not found.")
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def get_base_url() -> str:
|
|
487
|
+
"""Retrieve the SystemLink API base URL.
|
|
488
|
+
|
|
489
|
+
Preference order:
|
|
490
|
+
1. Environment variable SLCLI_API_URL (preferred) or SYSTEMLINK_API_URL
|
|
491
|
+
2. Active profile from config file
|
|
492
|
+
3. Combined keyring config (legacy)
|
|
493
|
+
4. Legacy keyring entry SYSTEMLINK_API_URL
|
|
494
|
+
5. Default fallback to localhost
|
|
495
|
+
"""
|
|
496
|
+
return get_base_url_resolution().value
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def get_web_url() -> str:
|
|
500
|
+
"""Return the SystemLink primary web UI URL.
|
|
501
|
+
|
|
502
|
+
Preference order:
|
|
503
|
+
1. Environment variable SLCLI_WEB_URL (preferred) or SYSTEMLINK_WEB_URL
|
|
504
|
+
2. Active profile from config file
|
|
505
|
+
3. Combined keyring config (legacy)
|
|
506
|
+
4. Legacy keyring entry SYSTEMLINK_WEB_URL
|
|
507
|
+
5. Derived from get_base_url()
|
|
508
|
+
"""
|
|
509
|
+
return get_web_url_resolution().value
|
|
406
510
|
|
|
407
511
|
|
|
408
512
|
def _get_keyring_config() -> Dict[str, Any]:
|
|
@@ -430,46 +534,12 @@ def get_api_key() -> str:
|
|
|
430
534
|
"""Retrieve the SystemLink API key.
|
|
431
535
|
|
|
432
536
|
Preference order:
|
|
433
|
-
1. Environment variable
|
|
537
|
+
1. Environment variable SLCLI_API_KEY (preferred) or SYSTEMLINK_API_KEY
|
|
434
538
|
2. Active profile from config file
|
|
435
539
|
3. Combined keyring config (legacy)
|
|
436
540
|
4. Legacy keyring entry SYSTEMLINK_API_KEY
|
|
437
541
|
"""
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
# First, check environment variable (highest priority for runtime overrides)
|
|
441
|
-
api_key = os.environ.get("SYSTEMLINK_API_KEY")
|
|
442
|
-
if api_key:
|
|
443
|
-
return api_key
|
|
444
|
-
|
|
445
|
-
# Second, try the active profile
|
|
446
|
-
try:
|
|
447
|
-
from .profiles import get_active_profile
|
|
448
|
-
|
|
449
|
-
profile = get_active_profile()
|
|
450
|
-
if profile and profile.api_key:
|
|
451
|
-
return profile.api_key
|
|
452
|
-
except (FileNotFoundError, json.JSONDecodeError, KeyError, AttributeError):
|
|
453
|
-
# Profile lookup error, fall back to keyring-based configuration
|
|
454
|
-
pass
|
|
455
|
-
|
|
456
|
-
# Third, consult combined keyring config if present (legacy)
|
|
457
|
-
cfg = _get_keyring_config()
|
|
458
|
-
if cfg and isinstance(cfg, dict):
|
|
459
|
-
maybe = cfg.get("api_key") or cfg.get("apiKey") or cfg.get("apiToken")
|
|
460
|
-
if maybe:
|
|
461
|
-
return str(maybe)
|
|
462
|
-
|
|
463
|
-
# Fourth, try legacy keyring entry
|
|
464
|
-
api_key = keyring.get_password("systemlink-cli", "SYSTEMLINK_API_KEY")
|
|
465
|
-
if api_key:
|
|
466
|
-
return api_key
|
|
467
|
-
|
|
468
|
-
click.echo(
|
|
469
|
-
"Error: API key not found. Please set the SYSTEMLINK_API_KEY "
|
|
470
|
-
"environment variable or run 'slcli login --profile <name>'."
|
|
471
|
-
)
|
|
472
|
-
raise click.ClickException("API key not found.")
|
|
542
|
+
return get_api_key_resolution().value
|
|
473
543
|
|
|
474
544
|
|
|
475
545
|
def get_headers(content_type: str = "") -> Dict[str, str]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/demo-complete-workflow/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/demo-complete-workflow/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/demo-test-plans/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/exercise-7-1-test-plans/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/exercise-7-1-test-plans/config.yaml
RENAMED
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/examples/spec-compliance-notebooks/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/analysis-recipes.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/filtering.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/routine-examples.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/slcli/references/troubleshooting.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-job-debugging/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.14 → systemlink_cli-1.13.16}/slcli/skills/systemlink-python-test/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|