systemlink-cli 1.24.0__tar.gz → 1.25.1__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.24.0 → systemlink_cli-1.25.1}/PKG-INFO +1 -1
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/pyproject.toml +2 -1
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/_version.py +1 -1
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/config_click.py +165 -21
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/main.py +46 -1
- systemlink_cli-1.25.1/slcli/pkce.py +613 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/platform.py +122 -52
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/profiles.py +16 -5
- systemlink_cli-1.25.1/slcli/systemlink-logo.svg +20 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/utils.py +182 -18
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workitem_click.py +14 -2
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/LICENSE +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/dff-editor/editor.js +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/dff-editor/index.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/__init__.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/__main__.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/alarm_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/comment_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/completion_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/config.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/dataframe_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/dff_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/example_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/example_loader.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/example_provisioner.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/README.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/_schema/schema-v1.0.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-1/README.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-1/config.yaml +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-2/README.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-2/config.yaml +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/config.yaml +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/leakage-current-results.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/overvoltage-results.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/product-xyz-compliance-report.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/product-xyz-specification.csv +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/thermal-response-results.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/feed_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/file_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/function_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/mcp_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/mcp_reachability.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/mcp_server.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/notebook_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/policy_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/rich_output.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/routine_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skill_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/SKILL.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/commands.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/example-authoring.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/import-specs.min.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/job-debugging/overview.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/nipkg/overview.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/notebook/interfaces.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/notebook/notebook-patterns.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/notebook/overview.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/python-test/overview.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/routine-examples.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/troubleshooting.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/angular-ui-packages.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/deployment.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/layout-patterns.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/nimble-angular.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/overview.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/systemlink-services.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/troubleshooting.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/scripts/spec_import_helper.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/spec_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/ssl_trust.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/state_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/system_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/system_query_utils.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/tag_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/templates_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/testmonitor_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/user_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/web_editor.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_bootstrap.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/.gitignore +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/README.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/angular.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/package.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app-routing.module.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app.module.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/state/app-view-state.service.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/systemlink-context.service.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/theme-sync.service.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/webapp-home-data.service.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/states/view-state.model.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/assets/README.md +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/index.html +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/main.ts +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/styles.scss +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/tsconfig.app.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/tsconfig.json +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workspace_click.py +0 -0
- {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workspace_utils.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "systemlink-cli"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.25.1"
|
|
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" }]
|
|
7
7
|
include = [
|
|
8
|
+
{ path = "slcli/systemlink-logo.svg", format = ["wheel", "sdist"] },
|
|
8
9
|
{ path = "dff-editor/index.html", format = ["wheel", "sdist"] },
|
|
9
10
|
{ path = "dff-editor/editor.js", format = ["wheel", "sdist"] },
|
|
10
11
|
{ path = "slcli/skills", format = ["wheel", "sdist"] },
|
|
@@ -16,6 +16,7 @@ from .platform import (
|
|
|
16
16
|
PLATFORM_SLE,
|
|
17
17
|
PLATFORM_SLS,
|
|
18
18
|
check_service_status,
|
|
19
|
+
check_web_server_auth,
|
|
19
20
|
)
|
|
20
21
|
from .profiles import ProfileConfig, Profile, check_config_file_permissions
|
|
21
22
|
from .rich_output import render_table
|
|
@@ -134,7 +135,11 @@ def _show_certificate_warning(certificate: dict[str, Any]) -> None:
|
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
def _trust_certificate_if_requested(
|
|
137
|
-
url: str,
|
|
138
|
+
url: str,
|
|
139
|
+
credential: str,
|
|
140
|
+
status: dict[str, Any],
|
|
141
|
+
trust_fingerprint: Optional[str],
|
|
142
|
+
auth_scheme: str = "api-key",
|
|
138
143
|
) -> dict[str, Any]:
|
|
139
144
|
"""Prompt for or apply certificate trust after a verification failure."""
|
|
140
145
|
certificate_details = status.get("certificate")
|
|
@@ -174,7 +179,7 @@ def _trust_certificate_if_requested(
|
|
|
174
179
|
|
|
175
180
|
try:
|
|
176
181
|
save_managed_certificate(certificate)
|
|
177
|
-
retry_status = check_service_status(url,
|
|
182
|
+
retry_status = check_service_status(url, credential, auth_scheme=auth_scheme)
|
|
178
183
|
except (OSError, ValueError) as exc:
|
|
179
184
|
_exit_with_validation_error(
|
|
180
185
|
f"Could not save the trusted certificate: {exc}. Profile was not saved.",
|
|
@@ -202,6 +207,10 @@ def _add_profile_impl(
|
|
|
202
207
|
set_current: bool,
|
|
203
208
|
readonly: bool,
|
|
204
209
|
trust_fingerprint: Optional[str] = None,
|
|
210
|
+
auth_mode: str = "api-key",
|
|
211
|
+
client_id: Optional[str] = None,
|
|
212
|
+
scopes: tuple[str, ...] = (),
|
|
213
|
+
callback_port: Optional[int] = None,
|
|
205
214
|
) -> None:
|
|
206
215
|
"""Shared implementation for add-profile and login commands.
|
|
207
216
|
|
|
@@ -217,6 +226,10 @@ def _add_profile_impl(
|
|
|
217
226
|
set_current: Whether to set as the current profile
|
|
218
227
|
readonly: Whether to enable readonly mode
|
|
219
228
|
trust_fingerprint: Optional SHA-256 fingerprint for non-interactive trust approval
|
|
229
|
+
auth_mode: Authentication flow to use
|
|
230
|
+
client_id: Public OAuth client ID for the PKCE login
|
|
231
|
+
scopes: OAuth scopes for the PKCE login
|
|
232
|
+
callback_port: Optional loopback callback port; zero selects an ephemeral port
|
|
220
233
|
"""
|
|
221
234
|
# Get profile name
|
|
222
235
|
if not profile:
|
|
@@ -231,11 +244,20 @@ def _add_profile_impl(
|
|
|
231
244
|
assert isinstance(url, str)
|
|
232
245
|
url = _normalize_base_url(url, "SystemLink API URL")
|
|
233
246
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
if auth_mode == "api-key":
|
|
248
|
+
# Get API key - either from flag or prompt
|
|
249
|
+
if not api_key:
|
|
250
|
+
api_key = getpass.getpass("Enter your SystemLink API key: ")
|
|
251
|
+
assert isinstance(api_key, str)
|
|
252
|
+
api_key = _normalize_api_key(api_key)
|
|
253
|
+
elif auth_mode == "pkce":
|
|
254
|
+
if api_key:
|
|
255
|
+
_exit_with_validation_error(
|
|
256
|
+
"--api-key cannot be combined with --auth pkce. "
|
|
257
|
+
"Use --auth api-key for the API-key login flow."
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
_exit_with_validation_error(f"Unsupported authentication flow: {auth_mode}")
|
|
239
261
|
|
|
240
262
|
# Normalize and validate web_url (prompt if not provided)
|
|
241
263
|
if not web_url:
|
|
@@ -244,12 +266,59 @@ def _add_profile_impl(
|
|
|
244
266
|
assert isinstance(web_url, str)
|
|
245
267
|
web_url = _normalize_base_url(web_url, "SystemLink Web UI URL")
|
|
246
268
|
|
|
247
|
-
|
|
269
|
+
pkce_result: Any = None
|
|
270
|
+
pkce_scopes = list(scopes) if scopes else ["openid", "profile", "email", "offline_access"]
|
|
271
|
+
if auth_mode == "pkce":
|
|
272
|
+
certificate_status = check_web_server_auth(web_url, "", auth_scheme="bearer")
|
|
273
|
+
if certificate_status.get("certificate_error") and not certificate_status.get(
|
|
274
|
+
"server_reachable"
|
|
275
|
+
):
|
|
276
|
+
_trust_certificate_if_requested(
|
|
277
|
+
web_url,
|
|
278
|
+
"",
|
|
279
|
+
certificate_status,
|
|
280
|
+
trust_fingerprint,
|
|
281
|
+
auth_scheme="bearer",
|
|
282
|
+
)
|
|
283
|
+
if not client_id:
|
|
284
|
+
client_id = click.prompt("PKCE client ID")
|
|
285
|
+
assert isinstance(client_id, str)
|
|
286
|
+
client_id = client_id.strip()
|
|
287
|
+
if not client_id:
|
|
288
|
+
_exit_with_validation_error("PKCE client ID cannot be empty.")
|
|
289
|
+
|
|
290
|
+
from .pkce import PkceError, perform_pkce_login
|
|
291
|
+
|
|
292
|
+
click.echo("Opening the SystemLink Web UI for authentication...")
|
|
293
|
+
try:
|
|
294
|
+
if callback_port is None:
|
|
295
|
+
pkce_result = perform_pkce_login(web_url, client_id, pkce_scopes)
|
|
296
|
+
else:
|
|
297
|
+
pkce_result = perform_pkce_login(
|
|
298
|
+
web_url, client_id, pkce_scopes, callback_port=callback_port
|
|
299
|
+
)
|
|
300
|
+
except PkceError as exc:
|
|
301
|
+
_exit_with_validation_error(f"PKCE login failed: {exc}")
|
|
302
|
+
api_key = pkce_result.access_token
|
|
303
|
+
|
|
304
|
+
assert isinstance(api_key, str)
|
|
305
|
+
|
|
306
|
+
# PKCE uses the Web URL for bearer service probes; API-key login uses the API URL.
|
|
248
307
|
click.echo("Checking server connectivity and services...")
|
|
249
|
-
|
|
308
|
+
if auth_mode == "pkce":
|
|
309
|
+
status = check_service_status(web_url, api_key, auth_scheme="bearer")
|
|
310
|
+
else:
|
|
311
|
+
status = check_service_status(url, api_key)
|
|
250
312
|
|
|
251
313
|
if status.get("certificate_error") and not status.get("server_reachable"):
|
|
252
|
-
|
|
314
|
+
validation_url = web_url if auth_mode == "pkce" else url
|
|
315
|
+
status = _trust_certificate_if_requested(
|
|
316
|
+
validation_url,
|
|
317
|
+
api_key,
|
|
318
|
+
status,
|
|
319
|
+
trust_fingerprint,
|
|
320
|
+
auth_scheme="bearer" if auth_mode == "pkce" else "api-key",
|
|
321
|
+
)
|
|
253
322
|
|
|
254
323
|
platform = status["platform"]
|
|
255
324
|
services = status.get("services", {})
|
|
@@ -262,16 +331,26 @@ def _add_profile_impl(
|
|
|
262
331
|
)
|
|
263
332
|
|
|
264
333
|
if status["auth_valid"] is False and _all_service_probes_unauthorized(services):
|
|
334
|
+
auth_failure_message = (
|
|
335
|
+
"PKCE bearer token validation failed. The server responded, but the token was not authorized. "
|
|
336
|
+
if auth_mode == "pkce"
|
|
337
|
+
else "API key validation failed. The server responded, but the key was not authorized. "
|
|
338
|
+
)
|
|
265
339
|
_exit_with_validation_error(
|
|
266
|
-
|
|
267
|
-
"Profile was not saved.",
|
|
340
|
+
auth_failure_message + "Profile was not saved.",
|
|
268
341
|
ExitCodes.PERMISSION_DENIED,
|
|
269
342
|
)
|
|
270
343
|
|
|
271
344
|
if status["auth_valid"] is not True:
|
|
272
|
-
|
|
345
|
+
verification_message = (
|
|
273
346
|
"Connected to the server, but profile verification was inconclusive. Check the "
|
|
274
|
-
"
|
|
347
|
+
"Web UI URL, bearer token, and service availability. Profile was not saved."
|
|
348
|
+
if auth_mode == "pkce"
|
|
349
|
+
else "Connected to the server, but profile verification was inconclusive. Check the "
|
|
350
|
+
"API URL, API key, and service availability. Profile was not saved."
|
|
351
|
+
)
|
|
352
|
+
_exit_with_validation_error(
|
|
353
|
+
verification_message,
|
|
275
354
|
ExitCodes.GENERAL_ERROR,
|
|
276
355
|
)
|
|
277
356
|
|
|
@@ -283,7 +362,10 @@ def _add_profile_impl(
|
|
|
283
362
|
else:
|
|
284
363
|
click.echo(" Platform: Unknown (will attempt all features)")
|
|
285
364
|
|
|
286
|
-
|
|
365
|
+
if auth_mode == "pkce":
|
|
366
|
+
click.echo(" PKCE bearer token: ✓ Authorized")
|
|
367
|
+
else:
|
|
368
|
+
click.echo(" API key: ✓ Authorized")
|
|
287
369
|
|
|
288
370
|
if status.get("file_query_endpoint") == "query-files":
|
|
289
371
|
click.echo(" File query: query-files")
|
|
@@ -315,13 +397,40 @@ def _add_profile_impl(
|
|
|
315
397
|
platform=platform,
|
|
316
398
|
workspace=workspace,
|
|
317
399
|
readonly=readonly,
|
|
400
|
+
auth_mode=auth_mode,
|
|
401
|
+
pkce_client_id=client_id if auth_mode == "pkce" else None,
|
|
402
|
+
pkce_scopes=pkce_scopes if auth_mode == "pkce" else None,
|
|
318
403
|
)
|
|
319
404
|
|
|
320
405
|
# Load config and add profile
|
|
321
406
|
cfg = ProfileConfig.load()
|
|
407
|
+
previous_profile = cfg.get_profile(profile)
|
|
408
|
+
previous_current_profile = cfg.current_profile
|
|
322
409
|
cfg.add_profile(new_profile, set_current=set_current)
|
|
323
410
|
cfg.save()
|
|
324
411
|
|
|
412
|
+
if auth_mode == "pkce" and pkce_result is not None:
|
|
413
|
+
from .pkce import save_pkce_credentials
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
save_pkce_credentials(
|
|
417
|
+
profile,
|
|
418
|
+
pkce_result.access_token,
|
|
419
|
+
pkce_result.refresh_token,
|
|
420
|
+
pkce_result.expires_at,
|
|
421
|
+
)
|
|
422
|
+
except Exception as exc:
|
|
423
|
+
if previous_profile is None:
|
|
424
|
+
cfg.profiles.pop(profile, None)
|
|
425
|
+
else:
|
|
426
|
+
cfg.profiles[profile] = previous_profile
|
|
427
|
+
cfg.current_profile = previous_current_profile
|
|
428
|
+
cfg.save()
|
|
429
|
+
_exit_with_validation_error(
|
|
430
|
+
f"Could not store PKCE credentials securely: {exc}.",
|
|
431
|
+
ExitCodes.GENERAL_ERROR,
|
|
432
|
+
)
|
|
433
|
+
|
|
325
434
|
click.echo(f"\n✓ Profile '{profile}' saved successfully.")
|
|
326
435
|
click.echo(f" Server: {url}")
|
|
327
436
|
click.echo(f" Web URL: {web_url}")
|
|
@@ -373,6 +482,7 @@ def register_config_commands(cli: Any) -> None:
|
|
|
373
482
|
item = {
|
|
374
483
|
"name": p.name,
|
|
375
484
|
"server": p.server,
|
|
485
|
+
"auth-mode": p.auth_mode,
|
|
376
486
|
"current": p.name == cfg.current_profile,
|
|
377
487
|
}
|
|
378
488
|
if p.web_url:
|
|
@@ -523,13 +633,16 @@ def register_config_commands(cli: Any) -> None:
|
|
|
523
633
|
if profile.platform:
|
|
524
634
|
rows.append(["Platform", profile.platform or "Unknown"])
|
|
525
635
|
|
|
526
|
-
if
|
|
527
|
-
|
|
636
|
+
if profile.auth_mode == "pkce":
|
|
637
|
+
rows.append(["Authentication", "PKCE (bearer token in keyring)"])
|
|
528
638
|
else:
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
639
|
+
if show_secrets:
|
|
640
|
+
api_key_display = profile.api_key
|
|
641
|
+
else:
|
|
642
|
+
api_key_display = (
|
|
643
|
+
"****" + profile.api_key[-4:] if len(profile.api_key) >= 4 else "****"
|
|
644
|
+
)
|
|
645
|
+
rows.append(["API Key", api_key_display])
|
|
533
646
|
|
|
534
647
|
if profile.workspace:
|
|
535
648
|
rows.append(["Workspace", profile.workspace])
|
|
@@ -735,6 +848,29 @@ def register_config_commands(cli: Any) -> None:
|
|
|
735
848
|
@click.option("--url", help="SystemLink API URL")
|
|
736
849
|
@click.option("--api-key", help="SystemLink API key")
|
|
737
850
|
@click.option("--web-url", help="SystemLink Web UI base URL")
|
|
851
|
+
@click.option(
|
|
852
|
+
"--auth",
|
|
853
|
+
"auth_mode",
|
|
854
|
+
type=click.Choice(["api-key", "pkce"]),
|
|
855
|
+
default="api-key",
|
|
856
|
+
show_default=True,
|
|
857
|
+
help="Authentication flow to use",
|
|
858
|
+
)
|
|
859
|
+
@click.option("--client-id", help="Public OAuth client ID for the PKCE prototype")
|
|
860
|
+
@click.option(
|
|
861
|
+
"--callback-port",
|
|
862
|
+
type=click.IntRange(0, 65535),
|
|
863
|
+
help="Loopback callback port; use 0 for an ephemeral port",
|
|
864
|
+
)
|
|
865
|
+
@click.option(
|
|
866
|
+
"--scope",
|
|
867
|
+
"scopes",
|
|
868
|
+
multiple=True,
|
|
869
|
+
help=(
|
|
870
|
+
"OAuth scope to request; may be repeated "
|
|
871
|
+
"(defaults to openid profile email offline_access)"
|
|
872
|
+
),
|
|
873
|
+
)
|
|
738
874
|
@click.option("--workspace", "-w", help="Default workspace for this profile")
|
|
739
875
|
@click.option(
|
|
740
876
|
"--set-current/--no-set-current",
|
|
@@ -758,6 +894,10 @@ def register_config_commands(cli: Any) -> None:
|
|
|
758
894
|
url: Optional[str],
|
|
759
895
|
api_key: Optional[str],
|
|
760
896
|
web_url: Optional[str],
|
|
897
|
+
auth_mode: str,
|
|
898
|
+
client_id: Optional[str],
|
|
899
|
+
callback_port: Optional[int],
|
|
900
|
+
scopes: tuple[str, ...],
|
|
761
901
|
workspace: Optional[str],
|
|
762
902
|
set_current: bool,
|
|
763
903
|
readonly: bool,
|
|
@@ -781,6 +921,10 @@ def register_config_commands(cli: Any) -> None:
|
|
|
781
921
|
url=url,
|
|
782
922
|
api_key=api_key,
|
|
783
923
|
web_url=web_url,
|
|
924
|
+
auth_mode=auth_mode,
|
|
925
|
+
client_id=client_id,
|
|
926
|
+
callback_port=callback_port,
|
|
927
|
+
scopes=scopes,
|
|
784
928
|
workspace=workspace,
|
|
785
929
|
set_current=set_current,
|
|
786
930
|
readonly=readonly,
|
|
@@ -415,6 +415,29 @@ def ca_info() -> None:
|
|
|
415
415
|
@click.option("--url", help="SystemLink API URL")
|
|
416
416
|
@click.option("--api-key", help="SystemLink API key")
|
|
417
417
|
@click.option("--web-url", help="SystemLink Web UI base URL")
|
|
418
|
+
@click.option(
|
|
419
|
+
"--auth",
|
|
420
|
+
"auth_mode",
|
|
421
|
+
type=click.Choice(["api-key", "pkce"]),
|
|
422
|
+
default="api-key",
|
|
423
|
+
show_default=True,
|
|
424
|
+
help="Authentication flow to use",
|
|
425
|
+
)
|
|
426
|
+
@click.option("--client-id", help="Public OAuth client ID for the PKCE prototype")
|
|
427
|
+
@click.option(
|
|
428
|
+
"--callback-port",
|
|
429
|
+
type=click.IntRange(0, 65535),
|
|
430
|
+
help="Loopback callback port; use 0 for an ephemeral port",
|
|
431
|
+
)
|
|
432
|
+
@click.option(
|
|
433
|
+
"--scope",
|
|
434
|
+
"scopes",
|
|
435
|
+
multiple=True,
|
|
436
|
+
help=(
|
|
437
|
+
"OAuth scope to request; may be repeated "
|
|
438
|
+
"(defaults to openid profile email offline_access)"
|
|
439
|
+
),
|
|
440
|
+
)
|
|
418
441
|
@click.option("--workspace", "-w", help="Default workspace for this profile")
|
|
419
442
|
@click.option(
|
|
420
443
|
"--set-current/--no-set-current",
|
|
@@ -438,6 +461,10 @@ def login(
|
|
|
438
461
|
url: Optional[str],
|
|
439
462
|
api_key: Optional[str],
|
|
440
463
|
web_url: Optional[str],
|
|
464
|
+
auth_mode: str,
|
|
465
|
+
client_id: Optional[str],
|
|
466
|
+
callback_port: Optional[int],
|
|
467
|
+
scopes: tuple[str, ...],
|
|
441
468
|
workspace: Optional[str],
|
|
442
469
|
set_current: bool,
|
|
443
470
|
readonly: bool,
|
|
@@ -464,6 +491,10 @@ def login(
|
|
|
464
491
|
url=url,
|
|
465
492
|
api_key=api_key,
|
|
466
493
|
web_url=web_url,
|
|
494
|
+
auth_mode=auth_mode,
|
|
495
|
+
client_id=client_id,
|
|
496
|
+
callback_port=callback_port,
|
|
497
|
+
scopes=scopes,
|
|
467
498
|
workspace=workspace,
|
|
468
499
|
set_current=set_current,
|
|
469
500
|
readonly=readonly,
|
|
@@ -486,6 +517,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
486
517
|
from .profiles import ProfileConfig
|
|
487
518
|
|
|
488
519
|
cfg = ProfileConfig.load()
|
|
520
|
+
removed_profiles: list[str] = []
|
|
489
521
|
|
|
490
522
|
if remove_all:
|
|
491
523
|
if not force:
|
|
@@ -497,6 +529,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
497
529
|
return
|
|
498
530
|
|
|
499
531
|
# Clear all profiles
|
|
532
|
+
removed_profiles = list(cfg.profiles)
|
|
500
533
|
cfg.profiles.clear()
|
|
501
534
|
cfg.current_profile = None
|
|
502
535
|
cfg.save()
|
|
@@ -517,6 +550,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
517
550
|
return
|
|
518
551
|
|
|
519
552
|
cfg.delete_profile(profile)
|
|
553
|
+
removed_profiles = [profile]
|
|
520
554
|
cfg.save()
|
|
521
555
|
click.echo(f"✓ Profile '{profile}' removed.")
|
|
522
556
|
|
|
@@ -536,12 +570,20 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
536
570
|
return
|
|
537
571
|
|
|
538
572
|
cfg.delete_profile(current)
|
|
573
|
+
removed_profiles = [current]
|
|
539
574
|
cfg.save()
|
|
540
575
|
click.echo(f"✓ Profile '{current}' removed.")
|
|
541
576
|
if cfg.current_profile:
|
|
542
577
|
click.echo(f" Current profile is now: {cfg.current_profile}")
|
|
543
578
|
|
|
544
579
|
# Also clean up legacy keyring entries
|
|
580
|
+
try:
|
|
581
|
+
from .pkce import delete_pkce_credentials
|
|
582
|
+
|
|
583
|
+
for removed_profile in removed_profiles:
|
|
584
|
+
delete_pkce_credentials(removed_profile)
|
|
585
|
+
except Exception:
|
|
586
|
+
pass
|
|
545
587
|
try:
|
|
546
588
|
keyring.delete_password("systemlink-cli", "SYSTEMLINK_API_KEY")
|
|
547
589
|
except Exception:
|
|
@@ -606,7 +648,10 @@ def info(format: str, skip_health: bool, debug: bool) -> None:
|
|
|
606
648
|
elif platform_info.get("server_reachable") is False:
|
|
607
649
|
status = "✗ Server unreachable"
|
|
608
650
|
elif platform_info.get("auth_valid") is False:
|
|
609
|
-
|
|
651
|
+
credential_name = (
|
|
652
|
+
"Bearer token" if active_profile and active_profile.auth_mode == "pkce" else "API key"
|
|
653
|
+
)
|
|
654
|
+
status = f"✗ {credential_name} unauthorized"
|
|
610
655
|
else:
|
|
611
656
|
status = "✓ Connected"
|
|
612
657
|
|