systemlink-cli 1.13.3__tar.gz → 1.13.5__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.3 → systemlink_cli-1.13.5}/PKG-INFO +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/dff-editor/editor.js +96 -21
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/pyproject.toml +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/_version.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/comment_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/completion_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/config_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/dataframe_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/dff_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/example_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/feed_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/file_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/main.py +52 -3
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/mcp_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/notebook_click.py +61 -6
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/policy_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/routine_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skill_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/spec_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/templates_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/testmonitor_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/web_editor.py +123 -16
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/webapp_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/workitem_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/workspace_click.py +1 -1
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/LICENSE +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/dff-editor/index.html +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/__init__.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/__main__.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/config.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/example_loader.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/example_provisioner.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/README.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/_schema/schema-v1.0.json +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/demo-complete-workflow/README.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/demo-test-plans/README.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/demo-test-plans/config.yaml +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/function_click.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/mcp_reachability.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/mcp_server.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/platform.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/profiles.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/rich_output.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/nipkg-file-package/SKILL.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/SKILL.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/references/commands.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/references/troubleshooting.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-job-debugging/SKILL.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-notebook/SKILL.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-notebook/references/interfaces.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-notebook/references/notebook-patterns.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-python-test/SKILL.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-webapp/SKILL.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-webapp/references/layout-patterns.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/ssl_trust.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/state_click.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/system_click.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/system_query_utils.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/tag_click.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/user_click.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/utils.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/workspace_utils.py +0 -0
|
@@ -651,9 +651,7 @@ function downloadConfiguration() {
|
|
|
651
651
|
const a = document.createElement('a');
|
|
652
652
|
a.href = url;
|
|
653
653
|
a.download = 'dff-configuration.json';
|
|
654
|
-
document.body.appendChild(a);
|
|
655
654
|
a.click();
|
|
656
|
-
document.body.removeChild(a);
|
|
657
655
|
URL.revokeObjectURL(url);
|
|
658
656
|
showStatus('Configuration downloaded', 'success');
|
|
659
657
|
} catch (e) {
|
|
@@ -682,53 +680,130 @@ function refreshTree() {
|
|
|
682
680
|
|
|
683
681
|
const config = currentConfig;
|
|
684
682
|
if (!config) return;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
683
|
+
|
|
684
|
+
treeView.replaceChildren();
|
|
685
|
+
treeView.appendChild(createTreeNode({
|
|
686
|
+
nodeId: 'root',
|
|
687
|
+
icon: '📄',
|
|
688
|
+
label: 'Root Configuration'
|
|
689
|
+
}));
|
|
690
|
+
|
|
689
691
|
if (config.configurations && config.configurations.length > 0) {
|
|
690
692
|
config.configurations.forEach((conf, i) => {
|
|
691
693
|
const confLabel = conf.displayText || conf.name || conf.key || ('Configuration ' + (i + 1));
|
|
692
|
-
|
|
693
|
-
|
|
694
|
+
treeView.appendChild(createTreeNode({
|
|
695
|
+
nodeId: `config-${i}`,
|
|
696
|
+
icon: '⚙️',
|
|
697
|
+
label: confLabel,
|
|
698
|
+
indent: 1,
|
|
699
|
+
editLabel: `Edit configuration: ${confLabel}`,
|
|
700
|
+
editTitle: 'Edit configuration',
|
|
701
|
+
onEdit: () => showEditDialog('configuration', i)
|
|
702
|
+
}));
|
|
703
|
+
|
|
694
704
|
if (conf.views && conf.views.length > 0) {
|
|
695
705
|
conf.views.forEach((view, vi) => {
|
|
696
|
-
|
|
706
|
+
const viewLabel = view.displayText || view.key;
|
|
707
|
+
treeView.appendChild(createTreeNode({
|
|
708
|
+
nodeId: `config-${i}-view-${vi}`,
|
|
709
|
+
icon: '👁️',
|
|
710
|
+
label: viewLabel,
|
|
711
|
+
indent: 2,
|
|
712
|
+
editLabel: `Edit view: ${viewLabel}`,
|
|
713
|
+
editTitle: 'Edit view',
|
|
714
|
+
onEdit: () => showEditDialog('view', i, vi)
|
|
715
|
+
}));
|
|
697
716
|
});
|
|
698
717
|
}
|
|
699
718
|
});
|
|
700
719
|
}
|
|
701
|
-
|
|
702
|
-
// Groups
|
|
720
|
+
|
|
703
721
|
if (config.groups && config.groups.length > 0) {
|
|
704
|
-
|
|
722
|
+
treeView.appendChild(createTreeSummaryNode('📁', `Groups (${config.groups.length})`, 1));
|
|
705
723
|
config.groups.forEach((group, i) => {
|
|
706
724
|
const groupLabel = group.displayText || group.key;
|
|
707
|
-
|
|
725
|
+
treeView.appendChild(createTreeNode({
|
|
726
|
+
nodeId: `group-${i}`,
|
|
727
|
+
icon: '📦',
|
|
728
|
+
label: groupLabel,
|
|
729
|
+
indent: 2,
|
|
730
|
+
editLabel: `Edit group: ${groupLabel}`,
|
|
731
|
+
editTitle: 'Edit group',
|
|
732
|
+
onEdit: () => showEditDialog('group', i)
|
|
733
|
+
}));
|
|
708
734
|
});
|
|
709
735
|
}
|
|
710
|
-
|
|
711
|
-
// Fields
|
|
736
|
+
|
|
712
737
|
if (config.fields && config.fields.length > 0) {
|
|
713
|
-
|
|
738
|
+
treeView.appendChild(createTreeSummaryNode('📁', `Fields (${config.fields.length})`, 1));
|
|
714
739
|
config.fields.forEach((field, i) => {
|
|
715
740
|
const icon = field.required ? '🏷️' : '🔖';
|
|
716
741
|
const fieldLabel = field.displayText || field.key;
|
|
717
|
-
|
|
742
|
+
treeView.appendChild(createTreeNode({
|
|
743
|
+
nodeId: `field-${i}`,
|
|
744
|
+
icon,
|
|
745
|
+
label: fieldLabel,
|
|
746
|
+
indent: 2,
|
|
747
|
+
editLabel: `Edit field: ${fieldLabel}`,
|
|
748
|
+
editTitle: 'Edit field',
|
|
749
|
+
onEdit: () => showEditDialog('field', i)
|
|
750
|
+
}));
|
|
718
751
|
});
|
|
719
752
|
}
|
|
720
|
-
|
|
721
|
-
treeView.innerHTML = html;
|
|
722
753
|
}
|
|
723
754
|
|
|
724
|
-
function
|
|
755
|
+
function createTreeSummaryNode(icon, label, indent = 0) {
|
|
756
|
+
const node = document.createElement('div');
|
|
757
|
+
node.className = 'tree-node';
|
|
758
|
+
if (indent > 0) {
|
|
759
|
+
node.classList.add(`indent-${indent}`);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const iconElement = document.createElement('span');
|
|
763
|
+
iconElement.className = 'tree-icon';
|
|
764
|
+
iconElement.textContent = icon;
|
|
765
|
+
node.appendChild(iconElement);
|
|
766
|
+
|
|
767
|
+
const labelElement = document.createElement('span');
|
|
768
|
+
labelElement.textContent = label;
|
|
769
|
+
node.appendChild(labelElement);
|
|
770
|
+
|
|
771
|
+
return node;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function createTreeNode({ nodeId, icon, label, indent = 0, editLabel = '', editTitle = '', onEdit = null }) {
|
|
775
|
+
const node = createTreeSummaryNode(icon, label, indent);
|
|
776
|
+
node.dataset.nodeId = nodeId;
|
|
777
|
+
node.addEventListener('click', () => selectTreeNode(nodeId, node));
|
|
778
|
+
|
|
779
|
+
if (onEdit) {
|
|
780
|
+
const editButton = document.createElement('button');
|
|
781
|
+
editButton.className = 'edit-btn';
|
|
782
|
+
editButton.type = 'button';
|
|
783
|
+
editButton.textContent = '✎';
|
|
784
|
+
editButton.setAttribute('aria-label', editLabel);
|
|
785
|
+
editButton.title = editTitle;
|
|
786
|
+
editButton.addEventListener('click', (event) => {
|
|
787
|
+
event.stopPropagation();
|
|
788
|
+
onEdit();
|
|
789
|
+
});
|
|
790
|
+
node.appendChild(editButton);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return node;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function selectTreeNode(nodeId, nodeElement = null) {
|
|
725
797
|
selectedTreeNode = nodeId;
|
|
726
798
|
|
|
727
799
|
// Update visual selection
|
|
728
800
|
document.querySelectorAll('.tree-node').forEach(node => {
|
|
729
801
|
node.classList.remove('selected');
|
|
730
802
|
});
|
|
731
|
-
|
|
803
|
+
const activeNode = nodeElement || document.querySelector(`[data-node-id="${nodeId}"]`);
|
|
804
|
+
if (activeNode) {
|
|
805
|
+
activeNode.classList.add('selected');
|
|
806
|
+
}
|
|
732
807
|
|
|
733
808
|
// Highlight corresponding JSON in editor
|
|
734
809
|
selectNodeInEditor(nodeId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "systemlink-cli"
|
|
3
|
-
version = "1.13.
|
|
3
|
+
version = "1.13.5"
|
|
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" }]
|
|
@@ -188,7 +188,7 @@ def register_comment_commands(cli: Any) -> None:
|
|
|
188
188
|
@cli.group()
|
|
189
189
|
@click.pass_context
|
|
190
190
|
def comment(ctx: click.Context) -> None:
|
|
191
|
-
"""Manage
|
|
191
|
+
"""Manage SystemLink comments.
|
|
192
192
|
|
|
193
193
|
Comments can be attached to any resource identified by a resource type
|
|
194
194
|
and resource ID. Known resource types: testmonitor:Result, niapm:Asset,
|
|
@@ -386,7 +386,7 @@ def register_completion_command(cli: Any) -> None:
|
|
|
386
386
|
help="Install completion script to shell config file",
|
|
387
387
|
)
|
|
388
388
|
def completion(shell: Optional[str], install: bool) -> None:
|
|
389
|
-
"""Generate and optionally install
|
|
389
|
+
"""Generate shell completion scripts and optionally install them.
|
|
390
390
|
|
|
391
391
|
Examples:
|
|
392
392
|
# Generate bash completion script
|
|
@@ -225,7 +225,7 @@ def register_config_commands(cli: Any) -> None:
|
|
|
225
225
|
|
|
226
226
|
@cli.group()
|
|
227
227
|
def config() -> None:
|
|
228
|
-
"""Manage slcli
|
|
228
|
+
"""Manage slcli settings and profiles.
|
|
229
229
|
|
|
230
230
|
Profiles allow you to configure multiple SystemLink environments
|
|
231
231
|
(dev, test, prod) and switch between them easily.
|
|
@@ -646,7 +646,7 @@ def register_dataframe_commands(cli: Any) -> None:
|
|
|
646
646
|
@cli.group()
|
|
647
647
|
@click.pass_context
|
|
648
648
|
def dataframe(ctx: click.Context) -> None:
|
|
649
|
-
"""Manage SystemLink DataFrame tables and
|
|
649
|
+
"""Manage SystemLink DataFrame tables and rows."""
|
|
650
650
|
if ctx.invoked_subcommand is not None:
|
|
651
651
|
require_feature("dataframe_service")
|
|
652
652
|
|
|
@@ -312,7 +312,7 @@ def register_dff_commands(cli: Any) -> None:
|
|
|
312
312
|
@cli.group(name="customfield")
|
|
313
313
|
@click.pass_context
|
|
314
314
|
def dff(ctx: click.Context) -> None:
|
|
315
|
-
"""Manage custom field
|
|
315
|
+
"""Manage SystemLink custom field configurations."""
|
|
316
316
|
# Check for platform feature availability
|
|
317
317
|
# Only check if a subcommand is being invoked (not just --help)
|
|
318
318
|
if ctx.invoked_subcommand is not None:
|
|
@@ -121,7 +121,7 @@ def register_example_commands(cli: Any) -> None:
|
|
|
121
121
|
|
|
122
122
|
@cli.group()
|
|
123
123
|
def example() -> None:
|
|
124
|
-
"""
|
|
124
|
+
"""Browse and provision example SystemLink resource configurations.
|
|
125
125
|
|
|
126
126
|
Examples help you quickly set up demo systems for training,
|
|
127
127
|
testing, or evaluation. Each example includes systems, assets,
|
|
@@ -436,7 +436,7 @@ def register_feed_commands(cli: Any) -> None:
|
|
|
436
436
|
|
|
437
437
|
@cli.group()
|
|
438
438
|
def feed() -> None:
|
|
439
|
-
"""Manage
|
|
439
|
+
"""Manage SystemLink package feeds and packages.
|
|
440
440
|
|
|
441
441
|
Feeds are package repositories used by NI Package Manager to install
|
|
442
442
|
software on test systems. Supports Windows (.nipkg) and NI Linux RT
|
|
@@ -49,6 +49,55 @@ else:
|
|
|
49
49
|
click = rich_click_module
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def _configure_rich_click_command_groups() -> None:
|
|
53
|
+
"""Configure top-level help command groups when rich-click is available."""
|
|
54
|
+
rich_click_config = getattr(click, "rich_click", None)
|
|
55
|
+
if rich_click_config is None:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Keep the command-name/help split consistent across top-level panels so
|
|
59
|
+
# descriptions start at the same column in every group.
|
|
60
|
+
rich_click_config.STYLE_COMMANDS_TABLE_EXPAND = True
|
|
61
|
+
rich_click_config.STYLE_COMMANDS_TABLE_COLUMN_WIDTH_RATIO = (1, 5)
|
|
62
|
+
|
|
63
|
+
rich_click_config.COMMAND_GROUPS = {
|
|
64
|
+
"slcli": [
|
|
65
|
+
{
|
|
66
|
+
"name": "Configure",
|
|
67
|
+
"commands": ["config", "login", "logout", "info", "completion"],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "Administer",
|
|
71
|
+
"commands": ["auth", "user", "workspace"],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "Operate",
|
|
75
|
+
"commands": [
|
|
76
|
+
"asset",
|
|
77
|
+
"system",
|
|
78
|
+
"state",
|
|
79
|
+
"tag",
|
|
80
|
+
"file",
|
|
81
|
+
"feed",
|
|
82
|
+
"comment",
|
|
83
|
+
"dataframe",
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "Build & Automate",
|
|
88
|
+
"commands": ["notebook", "routine", "webapp", "customfield", "skill", "mcp"],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "Validate & Plan",
|
|
92
|
+
"commands": ["testmonitor", "template", "spec", "workitem", "example"],
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
_configure_rich_click_command_groups()
|
|
99
|
+
|
|
100
|
+
|
|
52
101
|
def get_version() -> str:
|
|
53
102
|
"""Get version from _version.py (built binary) or pyproject.toml (development)."""
|
|
54
103
|
try:
|
|
@@ -193,7 +242,7 @@ def login(
|
|
|
193
242
|
set_current: bool,
|
|
194
243
|
readonly: bool,
|
|
195
244
|
) -> None:
|
|
196
|
-
"""
|
|
245
|
+
"""Create or update a SystemLink profile with credentials.
|
|
197
246
|
|
|
198
247
|
This is an alias for 'slcli config add'. Use that command
|
|
199
248
|
for the same functionality and more configuration options.
|
|
@@ -225,7 +274,7 @@ def login(
|
|
|
225
274
|
@click.option("--all", "remove_all", is_flag=True, help="Remove all profiles")
|
|
226
275
|
@click.option("--force", "-f", is_flag=True, help="Skip confirmation prompt")
|
|
227
276
|
def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
228
|
-
"""Remove stored SystemLink credentials.
|
|
277
|
+
"""Remove stored SystemLink profiles and credentials.
|
|
229
278
|
|
|
230
279
|
By default, removes the current profile. Use --profile to remove a specific
|
|
231
280
|
profile, or --all to remove all profiles.
|
|
@@ -309,7 +358,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
309
358
|
@click.option("--format", "-f", type=click.Choice(["table", "json"]), default="table")
|
|
310
359
|
@click.option("--skip-health", is_flag=True, default=False, help="Skip live service health checks.")
|
|
311
360
|
def info(format: str, skip_health: bool) -> None:
|
|
312
|
-
"""Show
|
|
361
|
+
"""Show the active profile, configuration, and platform status."""
|
|
313
362
|
from .profiles import ProfileConfig, get_active_profile
|
|
314
363
|
|
|
315
364
|
platform_info = get_platform_info(skip_health=skip_health)
|
|
@@ -143,7 +143,7 @@ def register_mcp_commands(cli: Any) -> None:
|
|
|
143
143
|
|
|
144
144
|
@cli.group()
|
|
145
145
|
def mcp() -> None:
|
|
146
|
-
"""
|
|
146
|
+
"""Run and configure the SystemLink MCP server for AI assistants."""
|
|
147
147
|
|
|
148
148
|
@mcp.command(name="serve")
|
|
149
149
|
@click.option(
|
|
@@ -6,6 +6,7 @@ All commands use Click for robust CLI interfaces and error handling.
|
|
|
6
6
|
|
|
7
7
|
import datetime
|
|
8
8
|
import json
|
|
9
|
+
import re
|
|
9
10
|
import sys
|
|
10
11
|
import time
|
|
11
12
|
import urllib.parse
|
|
@@ -50,6 +51,56 @@ PREDEFINED_NOTEBOOK_INTERFACES = [
|
|
|
50
51
|
"Work Item Scheduler",
|
|
51
52
|
]
|
|
52
53
|
|
|
54
|
+
WINDOWS_RESERVED_FILENAMES = {
|
|
55
|
+
"CON",
|
|
56
|
+
"PRN",
|
|
57
|
+
"AUX",
|
|
58
|
+
"NUL",
|
|
59
|
+
"COM1",
|
|
60
|
+
"COM2",
|
|
61
|
+
"COM3",
|
|
62
|
+
"COM4",
|
|
63
|
+
"COM5",
|
|
64
|
+
"COM6",
|
|
65
|
+
"COM7",
|
|
66
|
+
"COM8",
|
|
67
|
+
"COM9",
|
|
68
|
+
"LPT1",
|
|
69
|
+
"LPT2",
|
|
70
|
+
"LPT3",
|
|
71
|
+
"LPT4",
|
|
72
|
+
"LPT5",
|
|
73
|
+
"LPT6",
|
|
74
|
+
"LPT7",
|
|
75
|
+
"LPT8",
|
|
76
|
+
"LPT9",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _get_safe_notebook_download_name(notebook_name: str, suffix: str) -> str:
|
|
81
|
+
"""Build a local filename from remote notebook metadata without honoring path segments."""
|
|
82
|
+
name_segment = notebook_name.replace("\\", "/").split("/")[-1].strip()
|
|
83
|
+
if name_segment in {"", ".", ".."}:
|
|
84
|
+
name_segment = "notebook"
|
|
85
|
+
|
|
86
|
+
stem = name_segment[:-6] if name_segment.lower().endswith(".ipynb") else name_segment
|
|
87
|
+
safe_stem = re.sub(r"[^A-Za-z0-9._ -]+", "_", stem).strip(" ._") or "notebook"
|
|
88
|
+
if safe_stem.upper() in WINDOWS_RESERVED_FILENAMES:
|
|
89
|
+
safe_stem = f"{safe_stem}_file"
|
|
90
|
+
normalized_suffix = suffix if suffix.startswith(".") else f".{suffix}"
|
|
91
|
+
|
|
92
|
+
return f"{safe_stem}{normalized_suffix}"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _get_safe_notebook_download_path(notebook_name: str, suffix: str) -> Path:
|
|
96
|
+
"""Build a safe local output path for remote-derived notebook filenames."""
|
|
97
|
+
safe_filename = _get_safe_notebook_download_name(notebook_name, suffix)
|
|
98
|
+
working_directory = Path.cwd().resolve()
|
|
99
|
+
output_path = (working_directory / safe_filename).resolve()
|
|
100
|
+
if output_path.parent != working_directory:
|
|
101
|
+
raise ValueError("Notebook download path must stay within the current working directory")
|
|
102
|
+
return output_path
|
|
103
|
+
|
|
53
104
|
|
|
54
105
|
def _normalize_sls_notebook(notebook: Dict[str, Any]) -> None:
|
|
55
106
|
"""Normalize SLS notebook response to include id/name fields.
|
|
@@ -529,9 +580,10 @@ def _download_notebook_content_and_metadata(
|
|
|
529
580
|
if download_type in ("content", "both"):
|
|
530
581
|
try:
|
|
531
582
|
content = _get_notebook_content_http(notebook_id)
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
583
|
+
if output:
|
|
584
|
+
output_path = Path(output)
|
|
585
|
+
else:
|
|
586
|
+
output_path = _get_safe_notebook_download_path(notebook_name, ".ipynb")
|
|
535
587
|
with open(output_path, "wb") as f:
|
|
536
588
|
f.write(content)
|
|
537
589
|
click.echo(f"Notebook content downloaded to {output_path}")
|
|
@@ -543,14 +595,17 @@ def _download_notebook_content_and_metadata(
|
|
|
543
595
|
if download_type in ("metadata", "both"):
|
|
544
596
|
try:
|
|
545
597
|
meta = _get_notebook_http(notebook_id)
|
|
546
|
-
|
|
598
|
+
if output:
|
|
599
|
+
meta_path = Path(f"{output}.json")
|
|
600
|
+
else:
|
|
601
|
+
meta_path = _get_safe_notebook_download_path(notebook_name, ".json")
|
|
547
602
|
|
|
548
603
|
def _json_default(obj: Any) -> str:
|
|
549
604
|
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
550
605
|
return obj.isoformat()
|
|
551
606
|
return str(obj)
|
|
552
607
|
|
|
553
|
-
save_json_file(meta, meta_path, _json_default)
|
|
608
|
+
save_json_file(meta, str(meta_path), _json_default)
|
|
554
609
|
click.echo(f"Notebook metadata downloaded to {meta_path}")
|
|
555
610
|
except Exception as exc:
|
|
556
611
|
click.echo(f"Failed to download notebook metadata: {exc}")
|
|
@@ -574,7 +629,7 @@ def register_notebook_commands(cli: Any) -> None:
|
|
|
574
629
|
|
|
575
630
|
@cli.group()
|
|
576
631
|
def notebook() -> None: # pragma: no cover - Click wiring
|
|
577
|
-
"""
|
|
632
|
+
"""Create, run, and manage SystemLink notebooks."""
|
|
578
633
|
pass
|
|
579
634
|
|
|
580
635
|
# ------------------------------------------------------------------
|
|
@@ -32,7 +32,7 @@ def register_policy_commands(cli: Any) -> None:
|
|
|
32
32
|
|
|
33
33
|
@cli.group(name="auth")
|
|
34
34
|
def auth() -> None:
|
|
35
|
-
"""Manage SystemLink
|
|
35
|
+
"""Manage SystemLink authorization policies and policy templates."""
|
|
36
36
|
pass
|
|
37
37
|
|
|
38
38
|
@auth.group(name="policy")
|
|
@@ -127,7 +127,7 @@ def register_routine_commands(cli: Any) -> None:
|
|
|
127
127
|
|
|
128
128
|
@cli.group()
|
|
129
129
|
def routine() -> None:
|
|
130
|
-
"""Manage SystemLink routines
|
|
130
|
+
"""Manage SystemLink routines."""
|
|
131
131
|
pass
|
|
132
132
|
|
|
133
133
|
# ------------------------------------------------------------------
|
|
@@ -200,7 +200,7 @@ def register_skill_commands(cli: Any) -> None:
|
|
|
200
200
|
|
|
201
201
|
@cli.group()
|
|
202
202
|
def skill() -> None:
|
|
203
|
-
"""
|
|
203
|
+
"""Install and manage AI assistant skills."""
|
|
204
204
|
|
|
205
205
|
@skill.command(name="install")
|
|
206
206
|
@click.option(
|
|
@@ -1263,7 +1263,7 @@ def register_spec_commands(cli: Any) -> None:
|
|
|
1263
1263
|
|
|
1264
1264
|
@cli.group(name="spec")
|
|
1265
1265
|
def spec() -> None:
|
|
1266
|
-
"""Manage specifications."""
|
|
1266
|
+
"""Manage SystemLink specifications."""
|
|
1267
1267
|
pass
|
|
1268
1268
|
|
|
1269
1269
|
# -- list ---------------------------------------------------------------
|
|
@@ -120,7 +120,7 @@ def register_templates_commands(cli: Any) -> None:
|
|
|
120
120
|
@cli.group()
|
|
121
121
|
@click.pass_context
|
|
122
122
|
def template(ctx: click.Context) -> None:
|
|
123
|
-
"""Manage test plan templates."""
|
|
123
|
+
"""Manage SystemLink test plan templates."""
|
|
124
124
|
# Check for platform feature availability
|
|
125
125
|
# Only check if a subcommand is being invoked (not just --help)
|
|
126
126
|
if ctx.invoked_subcommand is not None:
|
|
@@ -735,7 +735,7 @@ def register_testmonitor_commands(cli: Any) -> None:
|
|
|
735
735
|
|
|
736
736
|
@cli.group()
|
|
737
737
|
def testmonitor() -> None:
|
|
738
|
-
"""
|
|
738
|
+
"""Manage SystemLink Test Monitor products and results."""
|
|
739
739
|
|
|
740
740
|
@testmonitor.group()
|
|
741
741
|
def product() -> None:
|
|
@@ -43,10 +43,9 @@ def _build_proxy_url(
|
|
|
43
43
|
origin_scheme: str,
|
|
44
44
|
origin_netloc: str,
|
|
45
45
|
target_path: str,
|
|
46
|
-
query: str = "",
|
|
47
46
|
) -> str:
|
|
48
47
|
"""Build a proxy URL from a validated origin and allowlisted path."""
|
|
49
|
-
return urllib.parse.urlunsplit((origin_scheme, origin_netloc, target_path,
|
|
48
|
+
return urllib.parse.urlunsplit((origin_scheme, origin_netloc, target_path, "", ""))
|
|
50
49
|
|
|
51
50
|
|
|
52
51
|
def _validated_proxy_path(request_path: str) -> str:
|
|
@@ -59,6 +58,118 @@ def _validated_proxy_path(request_path: str) -> str:
|
|
|
59
58
|
return decoded_path
|
|
60
59
|
|
|
61
60
|
|
|
61
|
+
def _validated_proxy_query_params(query: str) -> dict[str, list[str]]:
|
|
62
|
+
"""Return parsed proxy query parameters."""
|
|
63
|
+
if not query:
|
|
64
|
+
return {}
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
return urllib.parse.parse_qs(
|
|
68
|
+
query,
|
|
69
|
+
keep_blank_values=True,
|
|
70
|
+
strict_parsing=True,
|
|
71
|
+
separator="&",
|
|
72
|
+
)
|
|
73
|
+
except ValueError as exc:
|
|
74
|
+
raise ValueError("Editor proxy received an invalid query string") from exc
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _validated_single_query_value(
|
|
78
|
+
query_params: dict[str, list[str]],
|
|
79
|
+
name: str,
|
|
80
|
+
) -> str:
|
|
81
|
+
"""Return a single query value and reject repeated parameters."""
|
|
82
|
+
values = query_params.get(name)
|
|
83
|
+
if not values:
|
|
84
|
+
raise ValueError(f"Editor proxy requires query parameter: {name}")
|
|
85
|
+
if len(values) != 1:
|
|
86
|
+
raise ValueError(f"Editor proxy rejects repeated query parameter: {name}")
|
|
87
|
+
return values[0]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _validated_integer_query_value(
|
|
91
|
+
query_params: dict[str, list[str]],
|
|
92
|
+
name: str,
|
|
93
|
+
*,
|
|
94
|
+
minimum: int,
|
|
95
|
+
maximum: Optional[int] = None,
|
|
96
|
+
) -> str:
|
|
97
|
+
"""Return a validated integer query value preserved as a string."""
|
|
98
|
+
raw_value = _validated_single_query_value(query_params, name)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
parsed_value = int(raw_value)
|
|
102
|
+
except ValueError as exc:
|
|
103
|
+
raise ValueError(f"Editor proxy requires an integer query parameter: {name}") from exc
|
|
104
|
+
|
|
105
|
+
if parsed_value < minimum or (maximum is not None and parsed_value > maximum):
|
|
106
|
+
raise ValueError(f"Editor proxy rejected out-of-range query parameter: {name}")
|
|
107
|
+
|
|
108
|
+
return str(parsed_value)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _validated_identifier_query_value(query_params: dict[str, list[str]], name: str) -> str:
|
|
112
|
+
"""Return a single identifier-like query value safe for proxy forwarding."""
|
|
113
|
+
value = _validated_single_query_value(query_params, name)
|
|
114
|
+
if not value.strip():
|
|
115
|
+
raise ValueError(f"Editor proxy requires a non-empty query parameter: {name}")
|
|
116
|
+
if len(value) > 256:
|
|
117
|
+
raise ValueError(f"Editor proxy rejected oversized query parameter: {name}")
|
|
118
|
+
if any(ord(character) < 32 for character in value):
|
|
119
|
+
raise ValueError(f"Editor proxy rejected invalid query parameter: {name}")
|
|
120
|
+
if any(character in value for character in "/\\?#"):
|
|
121
|
+
raise ValueError(f"Editor proxy rejected invalid query parameter: {name}")
|
|
122
|
+
return value
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _resolve_proxy_target(
|
|
126
|
+
method: str,
|
|
127
|
+
request_path: str,
|
|
128
|
+
query: str,
|
|
129
|
+
) -> Optional[tuple[str, dict[str, str]]]:
|
|
130
|
+
"""Resolve a frontend route to a fixed upstream path and validated params."""
|
|
131
|
+
query_params = _validated_proxy_query_params(query)
|
|
132
|
+
|
|
133
|
+
if method == "POST":
|
|
134
|
+
post_route_map = {
|
|
135
|
+
"/api/dff/configurations": "/nidynamicformfields/v1/configurations",
|
|
136
|
+
"/api/dff/update-configurations": "/nidynamicformfields/v1/update-configurations",
|
|
137
|
+
"/nidynamicformfields/v1/update-configurations": "/nidynamicformfields/v1/update-configurations",
|
|
138
|
+
}
|
|
139
|
+
target_path = post_route_map.get(request_path)
|
|
140
|
+
if target_path is None:
|
|
141
|
+
return None
|
|
142
|
+
if query_params:
|
|
143
|
+
raise ValueError("Editor proxy does not forward query parameters for this route")
|
|
144
|
+
return target_path, {}
|
|
145
|
+
|
|
146
|
+
if method == "GET" and request_path == "/niuser/v1/workspaces":
|
|
147
|
+
unexpected_params = set(query_params) - {"take", "skip"}
|
|
148
|
+
if unexpected_params:
|
|
149
|
+
raise ValueError("Editor proxy rejected unsupported workspace query parameters")
|
|
150
|
+
|
|
151
|
+
safe_params: dict[str, str] = {}
|
|
152
|
+
if "take" in query_params:
|
|
153
|
+
safe_params["take"] = _validated_integer_query_value(
|
|
154
|
+
query_params, "take", minimum=1, maximum=1000
|
|
155
|
+
)
|
|
156
|
+
if "skip" in query_params:
|
|
157
|
+
safe_params["skip"] = _validated_integer_query_value(query_params, "skip", minimum=0)
|
|
158
|
+
return "/niuser/v1/workspaces", safe_params
|
|
159
|
+
|
|
160
|
+
if method == "GET" and request_path == "/nidynamicformfields/v1/resolved-configuration":
|
|
161
|
+
unexpected_params = set(query_params) - {"configurationId"}
|
|
162
|
+
if unexpected_params:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
"Editor proxy rejected unsupported resolved-configuration query parameters"
|
|
165
|
+
)
|
|
166
|
+
return "/nidynamicformfields/v1/resolved-configuration", {
|
|
167
|
+
"configurationId": _validated_identifier_query_value(query_params, "configurationId")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
|
|
62
173
|
class DFFWebEditor:
|
|
63
174
|
"""Web-based editor for custom fields configurations."""
|
|
64
175
|
|
|
@@ -224,21 +335,17 @@ class DFFWebEditor:
|
|
|
224
335
|
self.send_error(404, "Config file not found")
|
|
225
336
|
return True
|
|
226
337
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if
|
|
234
|
-
target_path = path_map[request_path]
|
|
235
|
-
elif request_path.startswith("/nidynamicformfields/v1/"):
|
|
236
|
-
target_path = request_path
|
|
237
|
-
elif request_path.startswith("/niuser/v1/workspaces"):
|
|
238
|
-
target_path = request_path
|
|
239
|
-
else:
|
|
338
|
+
try:
|
|
339
|
+
resolved_target = _resolve_proxy_target(method, request_path, parsed.query)
|
|
340
|
+
except ValueError as exc:
|
|
341
|
+
self.send_error(400, str(exc))
|
|
342
|
+
return True
|
|
343
|
+
|
|
344
|
+
if resolved_target is None:
|
|
240
345
|
return False
|
|
241
346
|
|
|
347
|
+
target_path, target_params = resolved_target
|
|
348
|
+
|
|
242
349
|
# Require per-session secret on all proxied routes
|
|
243
350
|
req_secret = self.headers.get("X-Editor-Secret")
|
|
244
351
|
if not secret or req_secret != secret:
|
|
@@ -249,7 +356,6 @@ class DFFWebEditor:
|
|
|
249
356
|
origin_scheme=api_scheme,
|
|
250
357
|
origin_netloc=api_netloc,
|
|
251
358
|
target_path=target_path,
|
|
252
|
-
query=parsed.query,
|
|
253
359
|
)
|
|
254
360
|
|
|
255
361
|
headers = dict(default_headers)
|
|
@@ -266,6 +372,7 @@ class DFFWebEditor:
|
|
|
266
372
|
url=target_url,
|
|
267
373
|
headers=headers,
|
|
268
374
|
data=data,
|
|
375
|
+
params=target_params or None,
|
|
269
376
|
verify=ssl_verify,
|
|
270
377
|
)
|
|
271
378
|
except requests.RequestException as exc: # pragma: no cover
|
|
@@ -893,7 +893,7 @@ def register_webapp_commands(cli: Any) -> None:
|
|
|
893
893
|
|
|
894
894
|
@cli.group()
|
|
895
895
|
def webapp() -> None: # pragma: no cover - Click wiring
|
|
896
|
-
"""
|
|
896
|
+
"""Build, publish, and manage SystemLink web applications."""
|
|
897
897
|
|
|
898
898
|
@webapp.command(name="init")
|
|
899
899
|
@click.argument(
|
|
@@ -431,7 +431,7 @@ def register_workitem_commands(cli: Any) -> None:
|
|
|
431
431
|
|
|
432
432
|
@cli.group()
|
|
433
433
|
def workitem() -> None:
|
|
434
|
-
"""Manage work items, templates, and workflows."""
|
|
434
|
+
"""Manage SystemLink work items, templates, and workflows."""
|
|
435
435
|
|
|
436
436
|
# -----------------------------------------------------------------------
|
|
437
437
|
# workitem list
|
|
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.3 → systemlink_cli-1.13.5}/slcli/examples/demo-complete-workflow/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/demo-complete-workflow/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/exercise-7-1-test-plans/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/exercise-7-1-test-plans/config.yaml
RENAMED
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/spec-compliance-notebooks/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/examples/spec-compliance-notebooks/config.yaml
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
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/references/analysis-recipes.md
RENAMED
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/references/datasheet-workflow.md
RENAMED
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/slcli/references/troubleshooting.md
RENAMED
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/slcli/skills/systemlink-job-debugging/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.13.3 → systemlink_cli-1.13.5}/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
|