systemlink-cli 1.18.1__tar.gz → 1.19.0__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 (146) hide show
  1. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/PKG-INFO +1 -1
  2. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/pyproject.toml +1 -1
  3. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/_version.py +1 -1
  4. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/mcp_server.py +19 -4
  5. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/commands.md +1 -0
  6. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/systemlink-services.md +40 -37
  7. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/tag_click.py +246 -0
  8. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/utils.py +8 -2
  9. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/LICENSE +0 -0
  10. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/dff-editor/editor.js +0 -0
  11. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/dff-editor/index.html +0 -0
  12. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/__init__.py +0 -0
  13. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/__main__.py +0 -0
  14. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/asset_click.py +0 -0
  15. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/cli_formatters.py +0 -0
  16. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/cli_utils.py +0 -0
  17. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/comment_click.py +0 -0
  18. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/completion_click.py +0 -0
  19. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/config.py +0 -0
  20. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/config_click.py +0 -0
  21. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/dataframe_click.py +0 -0
  22. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/dff_click.py +0 -0
  23. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/dff_decorators.py +0 -0
  24. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/example_click.py +0 -0
  25. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/example_loader.py +0 -0
  26. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/example_provisioner.py +0 -0
  27. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/README.md +0 -0
  28. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/_schema/schema-v1.0.json +0 -0
  29. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/demo-complete-workflow/README.md +0 -0
  30. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
  31. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/demo-test-plans/README.md +0 -0
  32. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/demo-test-plans/config.yaml +0 -0
  33. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
  34. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
  35. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
  36. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
  37. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
  38. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
  39. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
  40. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
  41. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
  42. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  43. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/feed_click.py +0 -0
  44. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/file_click.py +0 -0
  45. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/function_click.py +0 -0
  46. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/function_templates.py +0 -0
  47. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/main.py +0 -0
  48. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/mcp_click.py +0 -0
  49. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/mcp_reachability.py +0 -0
  50. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/notebook_click.py +0 -0
  51. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/platform.py +0 -0
  52. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/policy_click.py +0 -0
  53. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/policy_utils.py +0 -0
  54. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/profiles.py +0 -0
  55. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/response_handlers.py +0 -0
  56. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/rich_output.py +0 -0
  57. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/routine_click.py +0 -0
  58. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skill_click.py +0 -0
  59. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/SKILL.md +0 -0
  60. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
  61. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
  62. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/filtering.md +0 -0
  63. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/import-specs.min.json +0 -0
  64. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/job-debugging/overview.md +0 -0
  65. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/nipkg/overview.md +0 -0
  66. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/notebook/interfaces.md +0 -0
  67. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/notebook/notebook-patterns.md +0 -0
  68. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/notebook/overview.md +0 -0
  69. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/python-test/overview.md +0 -0
  70. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/routine-examples.md +0 -0
  71. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/troubleshooting.md +0 -0
  72. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/angular-ui-packages.md +0 -0
  73. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/deployment.md +0 -0
  74. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/layout-patterns.md +0 -0
  75. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/nimble-angular.md +0 -0
  76. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/overview.md +0 -0
  77. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/troubleshooting.md +0 -0
  78. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/skills/slcli/scripts/spec_import_helper.py +0 -0
  79. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/spec_click.py +0 -0
  80. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/ssl_trust.py +0 -0
  81. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/state_click.py +0 -0
  82. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/system_click.py +0 -0
  83. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/system_query_utils.py +0 -0
  84. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/table_utils.py +0 -0
  85. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/templates_click.py +0 -0
  86. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/testmonitor_click.py +0 -0
  87. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/universal_handlers.py +0 -0
  88. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/user_click.py +0 -0
  89. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/web_editor.py +0 -0
  90. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_bootstrap.py +0 -0
  91. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_click.py +0 -0
  92. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/.gitignore +0 -0
  93. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/README.md +0 -0
  94. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/angular.json +0 -0
  95. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/package.json +0 -0
  96. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app-routing.module.ts +0 -0
  97. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app.component.html +0 -0
  98. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app.component.scss +0 -0
  99. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app.component.ts +0 -0
  100. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app.module.ts +0 -0
  101. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.html +0 -0
  102. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.scss +0 -0
  103. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.ts +0 -0
  104. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/state/app-view-state.service.ts +0 -0
  105. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/systemlink-context.service.ts +0 -0
  106. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/theme-sync.service.ts +0 -0
  107. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/webapp-home-data.service.ts +0 -0
  108. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.html +0 -0
  109. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.scss +0 -0
  110. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.ts +0 -0
  111. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.html +0 -0
  112. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.scss +0 -0
  113. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.ts +0 -0
  114. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.html +0 -0
  115. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.scss +0 -0
  116. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.ts +0 -0
  117. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.html +0 -0
  118. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.scss +0 -0
  119. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.ts +0 -0
  120. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.html +0 -0
  121. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.scss +0 -0
  122. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.ts +0 -0
  123. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.html +0 -0
  124. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.scss +0 -0
  125. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.ts +0 -0
  126. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.html +0 -0
  127. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.scss +0 -0
  128. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.ts +0 -0
  129. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.html +0 -0
  130. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.scss +0 -0
  131. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.ts +0 -0
  132. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.html +0 -0
  133. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.scss +0 -0
  134. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.ts +0 -0
  135. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/states/view-state.model.ts +0 -0
  136. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/assets/README.md +0 -0
  137. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/index.html +0 -0
  138. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/main.ts +0 -0
  139. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/styles.scss +0 -0
  140. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/tsconfig.app.json +0 -0
  141. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/tsconfig.json +0 -0
  142. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/workflow_preview.py +0 -0
  143. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/workflows_click.py +0 -0
  144. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/workitem_click.py +0 -0
  145. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/workspace_click.py +0 -0
  146. {systemlink_cli-1.18.1 → systemlink_cli-1.19.0}/slcli/workspace_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: systemlink-cli
3
- Version: 1.18.1
3
+ Version: 1.19.0
4
4
  Summary: SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates.
5
5
  License-File: LICENSE
6
6
  Author: Fred Visser
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "systemlink-cli"
3
- version = "1.18.1"
3
+ version = "1.19.0"
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" }]
@@ -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.18.1"
4
+ __version__ = "1.19.0"
@@ -254,12 +254,27 @@ def query_tag_history(path: str, take: int = 100) -> str:
254
254
  from .utils import get_base_url
255
255
 
256
256
  path = _require(path, "path")
257
- encoded_path = urllib.parse.quote(path, safe="")
258
- url = f"{get_base_url()}/nitag/v2/tags/{encoded_path}/values/history?take={take}"
259
- data = _get_json(url)
257
+ url = f"{get_base_url()}/nitaghistorian/v2/tags/query-history"
258
+ data = _post_json(
259
+ url,
260
+ {
261
+ "path": path,
262
+ "startTime": "0001-01-01T00:00:00Z",
263
+ "endTime": "9999-12-31T23:59:59Z",
264
+ "take": take,
265
+ "sortOrder": "DESCENDING",
266
+ },
267
+ )
260
268
  if isinstance(data, list):
261
269
  return _dump(data)
262
- return _dump(data.get("values", data.get("tagsWithAggregates", [])))
270
+ values = data.get("values", [])
271
+ value_type = data.get("type")
272
+ if isinstance(values, list) and isinstance(value_type, str):
273
+ values = [
274
+ {**item, "type": value_type} if isinstance(item, dict) and "type" not in item else item
275
+ for item in values
276
+ ]
277
+ return _dump(values)
263
278
 
264
279
 
265
280
  @server.tool()
@@ -378,6 +378,7 @@ slcli dataframe delete <TABLE_ID>... [--yes]
378
378
  slcli tag list [OPTIONS] # List tags (filter by path glob, workspace)
379
379
  slcli tag get <TAG_PATH> [-f json] # Get tag metadata
380
380
  slcli tag get-value <TAG_PATH> # Read current tag value
381
+ slcli tag history <TAG_PATH> [-w WORKSPACE] [-t TAKE] [-f json] [--graph] # Read or graph history
381
382
  slcli tag set-value <TAG_PATH> <VALUE> # Write a tag value
382
383
  slcli tag create --path <PATH> --data-type <TYPE> # Create a new tag
383
384
  slcli tag update <TAG_PATH> [OPTIONS] # Update tag metadata
@@ -7,6 +7,7 @@ https://<server>/<service-prefix>/swagger/v2/<service-name>.yaml
7
7
  ```
8
8
 
9
9
  Examples:
10
+
10
11
  - `https://myserver.com/nitag/swagger/v2/nitag.yaml`
11
12
  - `https://myserver.com/nitest/swagger/v2/nitest.yaml`
12
13
 
@@ -14,18 +15,20 @@ Use the spec URL as the `input` in your `openapi-ts.config.ts`.
14
15
 
15
16
  ---
16
17
 
17
- ## Tag Historian (`/nitag`)
18
+ ## Tag Services
19
+
20
+ **Tag service base URL:** `window.location.origin + '/nitag'`
18
21
 
19
- **Base URL:** `window.location.origin + '/nitag'`
22
+ **Tag Historian service base URL:** `window.location.origin + '/nitaghistorian'`
20
23
 
21
24
  ### Key endpoints
22
25
 
23
- | Operation | Method | Path |
24
- |-----------|--------|------|
25
- | Query tags with current values | POST | `/nitag/v2/query-tags-with-values` |
26
- | Get single tag | GET | `/nitag/v2/tags/{path}` |
27
- | Write tag value | PUT | `/nitag/v2/tags/{path}/values/current` |
28
- | Query tag history | POST | `/nitag/v2/history-data/query-decimated-data` |
26
+ | Operation | Method | Path |
27
+ | ------------------------------ | ------ | --------------------------------------- |
28
+ | Query tags with current values | POST | `/nitag/v2/query-tags-with-values` |
29
+ | Get single tag | GET | `/nitag/v2/tags/{path}` |
30
+ | Write tag value | PUT | `/nitag/v2/tags/{path}/values/current` |
31
+ | Query tag history | POST | `/nitaghistorian/v2/tags/query-history` |
29
32
 
30
33
  ### Query body (`POST /query-tags-with-values`)
31
34
 
@@ -69,12 +72,12 @@ Use the spec URL as the `input` in your `openapi-ts.config.ts`.
69
72
 
70
73
  ### Key endpoints
71
74
 
72
- | Operation | Method | Path |
73
- |-----------|--------|------|
74
- | Query results | POST | `/nitest/v2/query-results` |
75
- | Get result by ID | GET | `/nitest/v2/results/{id}` |
76
- | Query steps | POST | `/nitest/v2/query-steps` |
77
- | Get products | GET | `/nitest/v2/products` |
75
+ | Operation | Method | Path |
76
+ | ---------------- | ------ | -------------------------- |
77
+ | Query results | POST | `/nitest/v2/query-results` |
78
+ | Get result by ID | GET | `/nitest/v2/results/{id}` |
79
+ | Query steps | POST | `/nitest/v2/query-steps` |
80
+ | Get products | GET | `/nitest/v2/products` |
78
81
 
79
82
  ### Query body
80
83
 
@@ -95,11 +98,11 @@ Use the spec URL as the `input` in your `openapi-ts.config.ts`.
95
98
 
96
99
  ### Key endpoints
97
100
 
98
- | Operation | Method | Path |
99
- |-----------|--------|------|
100
- | Query assets | POST | `/niapm/v1/query-assets` |
101
- | Get asset | GET | `/niapm/v1/assets/{id}` |
102
- | Get calibration forecast | GET | `/niapm/v1/assets/{id}/policies` |
101
+ | Operation | Method | Path |
102
+ | ------------------------ | ------ | -------------------------------- |
103
+ | Query assets | POST | `/niapm/v1/query-assets` |
104
+ | Get asset | GET | `/niapm/v1/assets/{id}` |
105
+ | Get calibration forecast | GET | `/niapm/v1/assets/{id}/policies` |
103
106
 
104
107
  ---
105
108
 
@@ -109,10 +112,10 @@ Use the spec URL as the `input` in your `openapi-ts.config.ts`.
109
112
 
110
113
  ### Key endpoints
111
114
 
112
- | Operation | Method | Path |
113
- |-----------|--------|------|
114
- | Get systems | GET | `/nisysmgmt/v1/systems` |
115
- | Query systems | POST | `/nisysmgmt/v1/query-systems` |
115
+ | Operation | Method | Path |
116
+ | ------------- | ------ | ----------------------------- |
117
+ | Get systems | GET | `/nisysmgmt/v1/systems` |
118
+ | Query systems | POST | `/nisysmgmt/v1/query-systems` |
116
119
 
117
120
  ---
118
121
 
@@ -122,10 +125,10 @@ Use the spec URL as the `input` in your `openapi-ts.config.ts`.
122
125
 
123
126
  ### Key endpoints
124
127
 
125
- | Operation | Method | Path |
126
- |-----------|--------|------|
127
- | Query work orders | POST | `/niworkorder/v1/query` |
128
- | Update work order | PATCH | `/niworkorder/v1/work-orders/{id}` |
128
+ | Operation | Method | Path |
129
+ | ----------------- | ------ | ---------------------------------- |
130
+ | Query work orders | POST | `/niworkorder/v1/query` |
131
+ | Update work order | PATCH | `/niworkorder/v1/work-orders/{id}` |
129
132
 
130
133
  ---
131
134
 
@@ -137,11 +140,11 @@ The app is served by SystemLink and calls SystemLink. Browser session cookies ar
137
140
 
138
141
  ```typescript
139
142
  // hey-api client setup
140
- import { createClient } from '@hey-api/client-fetch';
143
+ import { createClient } from "@hey-api/client-fetch";
141
144
 
142
145
  export const apiClient = createClient({
143
146
  baseUrl: `${window.location.origin}/nitag`,
144
- credentials: 'include',
147
+ credentials: "include",
145
148
  });
146
149
  ```
147
150
 
@@ -153,7 +156,7 @@ No API key needed. The user must be logged in to SystemLink in that browser tab.
153
156
  export const apiClient = createClient({
154
157
  baseUrl: `${window.location.origin}/nitag`,
155
158
  headers: {
156
- 'x-ni-api-key': localStorage.getItem('sl_api_key') ?? '',
159
+ "x-ni-api-key": localStorage.getItem("sl_api_key") ?? "",
157
160
  },
158
161
  });
159
162
  ```
@@ -174,7 +177,7 @@ path = "cpu" or path = "mem" OR
174
177
  keywords.Contains("production") array contains
175
178
  ```
176
179
 
177
- String values must be double-quoted in the filter string. Build filters by joining parts with ` and `.
180
+ String values must be double-quoted in the filter string. Build filters by joining parts with ` and `, including the surrounding spaces.
178
181
 
179
182
  ---
180
183
 
@@ -182,11 +185,11 @@ String values must be double-quoted in the filter string. Build filters by joini
182
185
 
183
186
  SystemLink APIs return standard HTTP status codes. Common ones:
184
187
 
185
- | Code | Meaning |
186
- |------|---------|
187
- | 401 | Not authenticated — user needs to log in, or API key is invalid |
188
- | 403 | Authenticated but not authorized for this workspace/resource |
189
- | 404 | Resource not found |
190
- | 422 | Invalid request body (e.g., bad LINQ filter) |
188
+ | Code | Meaning |
189
+ | ---- | --------------------------------------------------------------- |
190
+ | 401 | Not authenticated — user needs to log in, or API key is invalid |
191
+ | 403 | Authenticated but not authorized for this workspace/resource |
192
+ | 404 | Resource not found |
193
+ | 422 | Invalid request body (e.g., bad LINQ filter) |
191
194
 
192
195
  Show errors in a `<nimble-banner severity="error">` with the status code and message from the response body.
@@ -5,6 +5,7 @@ tag values. All tag operations are scoped to workspaces with proper error handli
5
5
  """
6
6
 
7
7
  import json
8
+ import math
8
9
  import shutil
9
10
  import sys
10
11
  import urllib.parse
@@ -24,6 +25,8 @@ from .utils import (
24
25
  )
25
26
  from .workspace_utils import resolve_workspace_id
26
27
 
28
+ _TAG_HISTORY_GRAPH_HEIGHT = 6
29
+
27
30
 
28
31
  def _tag_formatter(item: Dict[str, Any]) -> List[str]:
29
32
  """Format a tag for table output.
@@ -52,6 +55,124 @@ def _tag_formatter(item: Dict[str, Any]) -> List[str]:
52
55
  return [path, tag_type, value, last_updated]
53
56
 
54
57
 
58
+ def _tag_history_formatter(item: Dict[str, Any]) -> List[str]:
59
+ """Format one historical tag value for table output."""
60
+ value_data = item.get("value", {})
61
+ if isinstance(value_data, dict):
62
+ value = value_data.get("value", "N/A")
63
+ tag_type = value_data.get("type", item.get("type", "N/A"))
64
+ else:
65
+ value = value_data if value_data is not None else "N/A"
66
+ tag_type = item.get("type", "N/A")
67
+
68
+ return [
69
+ str(item.get("timestamp", "N/A")),
70
+ str(value),
71
+ str(tag_type),
72
+ ]
73
+
74
+
75
+ def _tag_history_numeric_value(item: Dict[str, Any]) -> Optional[float]:
76
+ """Return a finite numeric history value, if one is available."""
77
+ value_data = item.get("value")
78
+ value = value_data.get("value") if isinstance(value_data, dict) else value_data
79
+ if value is None or isinstance(value, bool):
80
+ return None
81
+
82
+ try:
83
+ numeric_value = float(value)
84
+ except (TypeError, ValueError):
85
+ return None
86
+
87
+ return numeric_value if math.isfinite(numeric_value) else None
88
+
89
+
90
+ def _downsample_history(values: List[float], width: int) -> List[float]:
91
+ """Downsample values to fit the available graph width."""
92
+ if len(values) <= width:
93
+ return values
94
+ if width <= 1:
95
+ return [values[-1]]
96
+
97
+ return [values[round(index * (len(values) - 1) / (width - 1))] for index in range(width)]
98
+
99
+
100
+ def _format_graph_value(value: float) -> str:
101
+ """Format a graph statistic without unnecessary trailing zeroes."""
102
+ return f"{value:.6g}"
103
+
104
+
105
+ def _render_tag_history_graph(
106
+ tag_path: str, workspace_label: str, history: List[Dict[str, Any]]
107
+ ) -> None:
108
+ """Render numeric tag history as a terminal sparkline."""
109
+ if not history:
110
+ click.echo(f"No tag history found in workspace '{workspace_label}'")
111
+ return
112
+
113
+ chronological_history = list(reversed(history))
114
+ numeric_values = [_tag_history_numeric_value(item) for item in chronological_history]
115
+ if any(value is None for value in numeric_values):
116
+ click.echo(
117
+ f"Cannot graph non-numeric tag history for '{tag_path}' "
118
+ f"in workspace '{workspace_label}'."
119
+ )
120
+ click.echo("Run without --graph to view the history table.")
121
+ return
122
+
123
+ values = [value for value in numeric_values if value is not None]
124
+ minimum = min(values)
125
+ maximum = max(values)
126
+ latest = values[-1]
127
+ terminal_width = max(shutil.get_terminal_size().columns, 1)
128
+ graph_width = max(1, min(len(values), terminal_width - 16))
129
+ graph_values = _downsample_history(values, graph_width)
130
+ value_range = maximum - minimum
131
+ point_rows = [
132
+ (
133
+ _TAG_HISTORY_GRAPH_HEIGHT // 2
134
+ if value_range == 0
135
+ else round((maximum - value) / value_range * (_TAG_HISTORY_GRAPH_HEIGHT - 1))
136
+ )
137
+ for value in graph_values
138
+ ]
139
+ graph_rows = [[" "] * len(graph_values) for _ in range(_TAG_HISTORY_GRAPH_HEIGHT)]
140
+ for index, point_row in enumerate(point_rows):
141
+ graph_rows[point_row][index] = "●"
142
+ if index == 0:
143
+ continue
144
+
145
+ previous_row = point_rows[index - 1]
146
+ if previous_row == point_row:
147
+ continue
148
+
149
+ step = 1 if point_row > previous_row else -1
150
+ for row in range(previous_row + step, point_row, step):
151
+ graph_rows[row][index] = "│"
152
+
153
+ first_timestamp = chronological_history[0].get("timestamp", "N/A")
154
+ last_timestamp = chronological_history[-1].get("timestamp", "N/A")
155
+ click.echo(f"Tag history graph for '{tag_path}'")
156
+ click.echo(f"Workspace: {workspace_label}")
157
+ click.echo(f"Range: {first_timestamp} to {last_timestamp}")
158
+ click.echo(
159
+ f"Min: {_format_graph_value(minimum)} "
160
+ f"Max: {_format_graph_value(maximum)} "
161
+ f"Latest: {_format_graph_value(latest)}"
162
+ )
163
+ for row, graph_row in enumerate(graph_rows):
164
+ if value_range == 0:
165
+ row_label = (
166
+ _format_graph_value(maximum) if row == _TAG_HISTORY_GRAPH_HEIGHT // 2 else ""
167
+ )
168
+ else:
169
+ row_value = maximum - value_range * row / (_TAG_HISTORY_GRAPH_HEIGHT - 1)
170
+ row_label = _format_graph_value(row_value)
171
+ click.echo(f" {row_label:>8} ┤{''.join(graph_row)}")
172
+ click.echo(f" {_format_graph_value(minimum):>8} └{'─' * len(graph_values)}")
173
+ click.echo(f" oldest{' ' * max(len(graph_values) - 12, 1)}newest")
174
+
175
+
55
176
  def _calculate_column_widths() -> List[int]:
56
177
  """Calculate dynamic column widths based on terminal size.
57
178
 
@@ -164,6 +285,59 @@ def _detect_value_type(value_str: str) -> Tuple[Any, str]:
164
285
  return value_str, "STRING"
165
286
 
166
287
 
288
+ def _get_tag_history(tag_path: str, workspace_id: Optional[str], take: int) -> List[Dict[str, Any]]:
289
+ """Fetch historical values for a tag.
290
+
291
+ Args:
292
+ tag_path: Tag path.
293
+ workspace_id: Optional workspace ID.
294
+ take: Maximum number of history entries to return.
295
+
296
+ Returns:
297
+ Historical tag value dictionaries, most recent first.
298
+ """
299
+ query_payload: Dict[str, Any] = {
300
+ "path": tag_path,
301
+ "startTime": "0001-01-01T00:00:00Z",
302
+ "endTime": "9999-12-31T23:59:59Z",
303
+ "take": take,
304
+ "sortOrder": "DESCENDING",
305
+ }
306
+ if workspace_id:
307
+ query_payload["workspace"] = workspace_id
308
+
309
+ url = f"{get_base_url()}/nitaghistorian/v2/tags/query-history"
310
+ resp = make_api_request("POST", url, payload=query_payload)
311
+ data = resp.json()
312
+
313
+ if isinstance(data, list):
314
+ return [item for item in data if isinstance(item, dict)]
315
+
316
+ if not isinstance(data, dict):
317
+ return []
318
+
319
+ value_type = data.get("type")
320
+
321
+ def normalize_entries(entries: Any) -> List[Dict[str, Any]]:
322
+ if not isinstance(entries, list):
323
+ return []
324
+ normalized: List[Dict[str, Any]] = []
325
+ for item in entries:
326
+ if not isinstance(item, dict):
327
+ continue
328
+ if isinstance(value_type, str) and "type" not in item:
329
+ item = {**item, "type": value_type}
330
+ normalized.append(item)
331
+ return normalized
332
+
333
+ for key in ("values", "history", "tagsWithAggregates", "data"):
334
+ entries = data.get(key)
335
+ if isinstance(entries, list):
336
+ return normalize_entries(entries)
337
+
338
+ return []
339
+
340
+
167
341
  def register_tag_commands(cli: Any) -> None:
168
342
  """Register the 'tag' command group and its subcommands."""
169
343
 
@@ -320,6 +494,78 @@ def register_tag_commands(cli: Any) -> None:
320
494
  except Exception as exc:
321
495
  handle_api_error(exc)
322
496
 
497
+ @tag.command(name="history")
498
+ @click.argument("tag_path")
499
+ @click.option(
500
+ "--workspace",
501
+ "-w",
502
+ type=str,
503
+ default=None,
504
+ help="Workspace ID or name (defaults to default workspace)",
505
+ )
506
+ @click.option(
507
+ "--take",
508
+ "-t",
509
+ type=click.IntRange(min=1),
510
+ default=100,
511
+ show_default=True,
512
+ help="Maximum number of historical values to return",
513
+ )
514
+ @click.option(
515
+ "--format",
516
+ "-f",
517
+ type=click.Choice(["table", "json"]),
518
+ default="table",
519
+ show_default=True,
520
+ help="Output format",
521
+ )
522
+ @click.option(
523
+ "--graph",
524
+ is_flag=True,
525
+ help="Render numeric history as a terminal sparkline (table format only)",
526
+ )
527
+ def tag_history(
528
+ tag_path: str,
529
+ workspace: Optional[str],
530
+ take: int,
531
+ format: str,
532
+ graph: bool,
533
+ ) -> None:
534
+ """Show historical values for a tag.
535
+
536
+ TAG_PATH is the path identifier of the tag.
537
+ """
538
+ validate_output_format(format)
539
+ if graph and format == "json":
540
+ click.echo("✗ --graph cannot be used with --format json", err=True)
541
+ sys.exit(ExitCodes.INVALID_INPUT)
542
+
543
+ try:
544
+ ws_id = resolve_workspace_id(workspace)
545
+ history = _get_tag_history(tag_path, ws_id, take)
546
+ workspace_label = ws_id or workspace or "default workspace"
547
+
548
+ if graph:
549
+ _render_tag_history_graph(tag_path, workspace_label, history)
550
+ return
551
+
552
+ history_resp = FilteredResponse({"values": history})
553
+
554
+ UniversalResponseHandler.handle_list_response(
555
+ resp=history_resp,
556
+ data_key="values",
557
+ item_name="tag history entry",
558
+ format_output=format,
559
+ formatter_func=_tag_history_formatter,
560
+ headers=["Timestamp", "Value", "Type"],
561
+ column_widths=[28, 30, 12],
562
+ empty_message=f"No tag history found in workspace '{workspace_label}'",
563
+ enable_pagination=False,
564
+ page_size=take,
565
+ )
566
+ except Exception as exc:
567
+ handle_api_error(exc)
568
+
323
569
  @tag.command(name="get")
324
570
  @click.argument("tag_path")
325
571
  @click.option(
@@ -11,6 +11,7 @@ import click
11
11
  import keyring
12
12
  import requests
13
13
 
14
+ from . import ssl_trust
14
15
  from .rich_output import print_json
15
16
  from .ssl_trust import use_standard_ssl_context
16
17
 
@@ -585,8 +586,13 @@ def get_ssl_verify(server_uri: Optional[str] = None) -> Union[bool, str]:
585
586
  except (OSError, ValueError):
586
587
  pass
587
588
 
588
- if os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE"):
589
- return os.environ.get("REQUESTS_CA_BUNDLE") or os.environ["SSL_CERT_FILE"]
589
+ requests_ca_bundle = os.environ.get("REQUESTS_CA_BUNDLE")
590
+ if requests_ca_bundle:
591
+ return requests_ca_bundle
592
+
593
+ ssl_cert_file = os.environ.get("SSL_CERT_FILE")
594
+ if ssl_cert_file and not ssl_trust.OS_TRUST_INJECTED:
595
+ return ssl_cert_file
590
596
 
591
597
  return True
592
598
 
File without changes