xsoar-cli 2.3.0__tar.gz → 2.4.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.
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/CHANGELOG.md +24 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/PKG-INFO +1 -1
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/_version.py +2 -2
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/pyproject.toml +4 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/content/README.md +36 -8
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/content/commands.py +69 -16
- xsoar_cli-2.4.0/src/xsoar_cli/utilities/content.py +303 -0
- xsoar_cli-2.4.0/src/xsoar_cli/utilities/output.py +262 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/content.py +82 -6
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/conftest.py +63 -3
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_case.py +0 -2
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_content.py +193 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_config_file.py +1 -3
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_content_domain.py +170 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_content_filters.py +252 -31
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_content_handlers.py +0 -1
- xsoar_cli-2.4.0/tests/unit/test_output.py +130 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_validators.py +3 -4
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_version_check.py +0 -1
- xsoar_cli-2.3.0/src/xsoar_cli/utilities/content.py +0 -210
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/.github/workflows/main.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/.github/workflows/pull-request-open.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/.github/workflows/release.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/.github/workflows/update-changelog.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/.gitignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/CLAUDE.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/CONTRIBUTING.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/LICENSE.txt +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/cli.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/case/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/case/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/case/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/completions/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/completions/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/completions/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/config/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/config/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/config/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/content/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/execute/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/execute/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/execute/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/graph/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/graph/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/graph/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/integration/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/integration/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/integration/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/manifest/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/manifest/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/manifest/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/pack/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/pack/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/pack/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/plugins/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/plugins/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/plugins/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/rbac/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/rbac/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/commands/rbac/commands.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/configuration.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/error_handling/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/error_handling/connection.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/error_handling/http.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/log.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/plugins/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/plugins/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/plugins/manager.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/utilities/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/utilities/config_file.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/utilities/download_content_handlers.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/utilities/manifest.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/utilities/validators.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/utilities/version_check.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/artifact_providers/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/artifact_providers/azure.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/artifact_providers/base.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/artifact_providers/s3.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/cases.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/client.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/constants.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/execution.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/integrations.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/packs.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/src/xsoar_cli/xsoar_client/rbac.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_base.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_completions.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_config.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_execute.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_graph.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_manifest.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_pack.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/cli/test_plugins.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/conftest.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/cases/create.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/cases/get.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/content/automation_search.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/content/playbook_search.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/content/user_commands.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/integrations/instances.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/manifest/manifest_base.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/manifest/manifest_invalid.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/manifest/manifest_with_pack_not_on_server.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/manifest/server_base_response.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/manifest/server_base_response_missing_one_pack.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/manifest/server_base_response_with_updates.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/manifest/server_base_response_with_updates_and_one_extra.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/packs/installed.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/packs/installed_expired.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/rbac/roles.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/rbac/user_groups.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/fixtures/rbac/users.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Download/playbook-empty.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/.pack-ignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/.secrets-ignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/Author_image.png +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/Playbooks/GenericPlaybook.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/pack_metadata.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonScripts/.pack-ignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonScripts/.secrets-ignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Author_image.png +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonScripts/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_CommonScripts/pack_metadata.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/.pack-ignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/.secrets-ignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Author_image.png +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Playbooks/EDR_InitialTriage.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/test_data/basescript-dummy.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_EDR/pack_metadata.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_Layouts/.pack-ignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_Layouts/.secrets-ignore +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_Layouts/Author_image.png +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_Layouts/Layouts/layoutscontainer-GenericLayout.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_Layouts/README.md +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/MyOrg_Layouts/pack_metadata.json +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/mock_content/Packs/Not_applicable/Playbooks/Empty.yml +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/__init__.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/conftest.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_artifact_azure.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_artifact_base.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_artifact_s3.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_cases.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_client.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_error_handling.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_execution.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_integrations.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_main.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_manifest_utils.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_packs.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_plugin_manager.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/tests/unit/test_rbac.py +0 -0
- {xsoar_cli-2.3.0 → xsoar_cli-2.4.0}/uv.lock +0 -0
|
@@ -8,6 +8,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
10
|
|
|
11
|
+
- `content get-detached --type all` queries both scripts and playbooks and displays results for each type.
|
|
12
|
+
- `content list --output-format` option (choices: `table`, `json`, `plain`, default: `table`). `table` is human-readable aligned columns, `json` is machine-readable, and `plain` is tab-separated values suitable for piping to tools like `grep`.
|
|
13
|
+
- `content list --search TERM` option. Filters the listed items with a case-insensitive substring match against each item's id, name, and description. Content types and integration brands with no matching items are omitted, and a `No content matching '<term>' found.` message is shown when nothing matches.
|
|
14
|
+
- `content describe --type [script|playbook|command] NAME` command. Shows a single content item in detail: description, arguments, and inputs/outputs. Commands also show the integration brand and its configured instances (name and state). Supports `--output-format [table|json]` (default: `table`). See the [content README](src/xsoar_cli/commands/content/README.md) for details.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- `content get-detached` now prints a human-readable summary instead of raw JSON. Output is a count followed by one line per item in the form `<name> (ID: <id>)`, or `No detached <type> found` when there are none.
|
|
19
|
+
- **Breaking:** `content list` now defaults to a human-readable table instead of JSON. Use `--output-format json` for machine-readable output.
|
|
20
|
+
- `content list` playbook output now shows the playbook ID and description (read from the `comment` field) instead of the name and ID.
|
|
21
|
+
- `content list` command output now shows the command name and description and no longer includes the integration brand. The summary-level JSON structure for commands is now a list of `{name, description}` objects instead of plain command-name strings.
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
|
|
25
|
+
- **Breaking:** `content list --detail-level` option, along with its `short`, `extended`, and `full` levels. `content list` now always produces a curated summary. Per-item detail (arguments, inputs, outputs) is available through the `content describe` command.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- `content get-detached` now returns only the content items that are actually detached. The previous query returned all system items, so the command reported every system script (or playbook) as detached instead of just the detached ones. Results are now filtered on each item's `detached` field.
|
|
30
|
+
|
|
31
|
+
## [2.3.0] - 2026-06-04
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
11
35
|
- `execute command NAME [ARGS]...` runs an automation script or integration command against XSOAR, in the user's playground by default or a specific case with `--case-id`. See the [execute README](src/xsoar_cli/commands/execute/README.md) for details.
|
|
12
36
|
- `execute playbook NAME` starts a playbook in the user's playground, or a specific case with `--case-id`. See the [execute README](src/xsoar_cli/commands/execute/README.md) for details.
|
|
13
37
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xsoar-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: A command-line interface for managing Palo Alto Networks XSOAR (Cortex XSOAR)
|
|
5
5
|
Project-URL: Documentation, https://github.com/tlium/xsoar-cli#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/tlium/xsoar-cli/issues
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 4, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -68,6 +68,10 @@ exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
|
|
|
68
68
|
[tool.pytest.ini_options]
|
|
69
69
|
testpaths = ["tests"]
|
|
70
70
|
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
line-length = 140
|
|
73
|
+
extend-exclude = ["tests/mock_content"]
|
|
74
|
+
|
|
71
75
|
[dependency-groups]
|
|
72
76
|
dev = [
|
|
73
77
|
"pytest>=9.0.2",
|
|
@@ -4,42 +4,70 @@ Inspect and manage content items on your XSOAR server.
|
|
|
4
4
|
|
|
5
5
|
## Get Detached
|
|
6
6
|
|
|
7
|
-
List detached content items. Detached items are content items that
|
|
7
|
+
List detached content items. Detached items are content items that have been modified on the server and are no longer in sync with the installed content pack.
|
|
8
|
+
|
|
9
|
+
The output is a human-readable summary: a count followed by one line per item in the form `<name> (ID: <id>)`. When no detached items are found, the output is `No detached <type> found` (for example `No detached scripts found`). This applies to both `--type scripts` and `--type playbooks`.
|
|
8
10
|
|
|
9
11
|
**Syntax:** `xsoar-cli content get-detached [OPTIONS]`
|
|
10
12
|
|
|
11
13
|
**Options:**
|
|
12
14
|
- `--environment TEXT` - Target environment (default: uses default environment from config)
|
|
13
|
-
- `--type [scripts|playbooks|all]` - Type of content items to retrieve (
|
|
15
|
+
- `--type [scripts|playbooks|all]` - Type of content items to retrieve (required)
|
|
14
16
|
|
|
15
17
|
**Examples:**
|
|
16
18
|
```
|
|
17
|
-
xsoar-cli content get-detached
|
|
18
|
-
xsoar-cli content get-detached --environment prod
|
|
19
19
|
xsoar-cli content get-detached --type scripts
|
|
20
|
-
xsoar-cli content get-detached --type playbooks
|
|
20
|
+
xsoar-cli content get-detached --type playbooks
|
|
21
|
+
xsoar-cli content get-detached --type all
|
|
22
|
+
xsoar-cli content get-detached --type scripts --environment prod
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
## List
|
|
24
26
|
|
|
25
|
-
List available content items. Enumerates commands, playbooks and scripts available on the server.
|
|
27
|
+
List available content items. Enumerates commands, playbooks and scripts available on the server. Designed for discovery: a quick, scannable overview of what content exists, primarily to help humans and AI agents identify relevant items before working with them.
|
|
28
|
+
|
|
29
|
+
By default the output is a human-readable table. Use `--output-format json` for machine-readable output, or `--output-format plain` for tab-separated values that are easy to pipe to tools like `grep`.
|
|
26
30
|
|
|
27
31
|
**Syntax:** `xsoar-cli content list [OPTIONS]`
|
|
28
32
|
|
|
29
33
|
**Options:**
|
|
30
34
|
- `--environment TEXT` - Target environment (default: uses default environment from config)
|
|
31
35
|
- `--type [scripts|playbooks|commands|all]` - Type of content items to list (default: all)
|
|
32
|
-
- `--
|
|
36
|
+
- `--search TEXT` - Case-insensitive substring filter on item id, name, and description
|
|
37
|
+
- `--output-format [table|json|plain]` - Output format (default: table)
|
|
33
38
|
|
|
34
39
|
**Examples:**
|
|
35
40
|
```
|
|
36
41
|
xsoar-cli content list
|
|
37
42
|
xsoar-cli content list --environment prod
|
|
38
43
|
xsoar-cli content list --type commands
|
|
39
|
-
xsoar-cli content list --type commands --
|
|
44
|
+
xsoar-cli content list --type commands --search slack
|
|
45
|
+
xsoar-cli content list --type commands --output-format json
|
|
46
|
+
xsoar-cli content list --type scripts --output-format plain
|
|
40
47
|
xsoar-cli content list --type playbooks --environment dev
|
|
41
48
|
```
|
|
42
49
|
|
|
50
|
+
## Describe
|
|
51
|
+
|
|
52
|
+
Describe a single content item in detail. Looks up one script, playbook, or command by name and shows its description, arguments, and inputs/outputs. Commands also show the integration brand and its configured instances (name and state). Use this after `content list` to get the detail needed to actually use an item.
|
|
53
|
+
|
|
54
|
+
The lookup is case-insensitive. Scripts match on id, playbooks match on id or name (so a custom playbook with a UUID id resolves by its human-readable name), and commands match on the command name.
|
|
55
|
+
|
|
56
|
+
**Syntax:** `xsoar-cli content describe --type TYPE NAME`
|
|
57
|
+
|
|
58
|
+
**Options:**
|
|
59
|
+
- `--environment TEXT` - Target environment (default: uses default environment from config)
|
|
60
|
+
- `--type [script|playbook|command]` - Type of content item to describe (required)
|
|
61
|
+
- `--output-format [table|json]` - Output format (default: table)
|
|
62
|
+
|
|
63
|
+
**Examples:**
|
|
64
|
+
```
|
|
65
|
+
xsoar-cli content describe --type command servicenow-get-record
|
|
66
|
+
xsoar-cli content describe --type script AddDNBHostIndicatorToCase
|
|
67
|
+
xsoar-cli content describe --type playbook "Phishing Investigation - Generic v2"
|
|
68
|
+
xsoar-cli content describe --type command servicenow-get-record --output-format json
|
|
69
|
+
```
|
|
70
|
+
|
|
43
71
|
## Download
|
|
44
72
|
|
|
45
73
|
Download a content item by name from the XSOAR server.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import logging
|
|
3
2
|
import pathlib
|
|
4
3
|
import subprocess
|
|
@@ -7,8 +6,9 @@ from typing import TYPE_CHECKING
|
|
|
7
6
|
import click
|
|
8
7
|
|
|
9
8
|
from xsoar_cli.utilities.config_file import get_xsoar_config, load_config
|
|
10
|
-
from xsoar_cli.utilities.content import
|
|
9
|
+
from xsoar_cli.utilities.content import describe_item, filter_content, format_detached_summary, search_content
|
|
11
10
|
from xsoar_cli.utilities.download_content_handlers import HANDLERS, resolve_output_path
|
|
11
|
+
from xsoar_cli.utilities.output import DESCRIBE_OUTPUT_FORMATS, OUTPUT_FORMATS, format_content_output, format_describe
|
|
12
12
|
from xsoar_cli.utilities.validators import validate_xsoar_connectivity
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
@@ -28,7 +28,7 @@ def content() -> None:
|
|
|
28
28
|
@click.option(
|
|
29
29
|
"--type",
|
|
30
30
|
"content_type",
|
|
31
|
-
type=click.Choice(["scripts", "playbooks"], case_sensitive=False),
|
|
31
|
+
type=click.Choice(["scripts", "playbooks", "all"], case_sensitive=False),
|
|
32
32
|
required=True,
|
|
33
33
|
help="Type of content items to retrieve.",
|
|
34
34
|
)
|
|
@@ -39,9 +39,13 @@ def get_detached(ctx: click.Context, environment: str | None, content_type: str)
|
|
|
39
39
|
"""List detached content items."""
|
|
40
40
|
config = get_xsoar_config(ctx)
|
|
41
41
|
xsoar_client: Client = config.get_client(environment)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
|
|
43
|
+
types = ["scripts", "playbooks"] if content_type == "all" else [content_type]
|
|
44
|
+
sections = []
|
|
45
|
+
for t in types:
|
|
46
|
+
items = xsoar_client.content.get_detached(t)
|
|
47
|
+
sections.append(format_detached_summary(items, t))
|
|
48
|
+
click.echo("\n\n".join(sections))
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
# We name the command in the decorator here to avoid shadowing the builtin list.
|
|
@@ -56,16 +60,21 @@ def get_detached(ctx: click.Context, environment: str | None, content_type: str)
|
|
|
56
60
|
help="Type of content items to list.",
|
|
57
61
|
)
|
|
58
62
|
@click.option(
|
|
59
|
-
"--
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
"--search",
|
|
64
|
+
default=None,
|
|
65
|
+
help="Case-insensitive substring filter on item id, name, and description.",
|
|
66
|
+
)
|
|
67
|
+
@click.option(
|
|
68
|
+
"--output-format",
|
|
69
|
+
type=click.Choice(OUTPUT_FORMATS, case_sensitive=False),
|
|
70
|
+
default="table",
|
|
62
71
|
show_default=True,
|
|
63
|
-
help="
|
|
72
|
+
help="Output format. Use 'json' for machine-readable output.",
|
|
64
73
|
)
|
|
65
74
|
@click.pass_context
|
|
66
75
|
@load_config
|
|
67
76
|
@validate_xsoar_connectivity
|
|
68
|
-
def list_content(ctx: click.Context, environment: str | None, content_type: str,
|
|
77
|
+
def list_content(ctx: click.Context, environment: str | None, content_type: str, search: str | None, output_format: str) -> None:
|
|
69
78
|
"""
|
|
70
79
|
List available content items. Enumerates commands, playbooks and scripts
|
|
71
80
|
available on the server, primarily to facilitate better AI generated
|
|
@@ -73,16 +82,59 @@ def list_content(ctx: click.Context, environment: str | None, content_type: str,
|
|
|
73
82
|
config = get_xsoar_config(ctx)
|
|
74
83
|
xsoar_client: Client = config.get_client(environment)
|
|
75
84
|
json_blob = xsoar_client.content.list(content_type)
|
|
76
|
-
if detail_level == "full":
|
|
77
|
-
click.echo(json.dumps(json_blob, indent=4))
|
|
78
|
-
ctx.exit(0)
|
|
79
85
|
|
|
80
86
|
# Individual type calls return a bare list, normalize to dict for filter_content.
|
|
81
87
|
if isinstance(json_blob, list):
|
|
82
88
|
json_blob = {content_type: json_blob}
|
|
83
89
|
|
|
84
|
-
filtered = filter_content(json_blob
|
|
85
|
-
|
|
90
|
+
filtered = filter_content(json_blob)
|
|
91
|
+
if search:
|
|
92
|
+
filtered = search_content(filtered, search)
|
|
93
|
+
if not filtered:
|
|
94
|
+
click.echo(f"No content matching '{search}' found.")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
click.echo(format_content_output(filtered, output_format=output_format))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@click.command()
|
|
101
|
+
@click.option("--environment", default=None, help="Default environment set in config file.")
|
|
102
|
+
@click.option(
|
|
103
|
+
"--type",
|
|
104
|
+
"content_type",
|
|
105
|
+
type=click.Choice(["script", "playbook", "command"], case_sensitive=False),
|
|
106
|
+
required=True,
|
|
107
|
+
help="Type of content item to describe.",
|
|
108
|
+
)
|
|
109
|
+
@click.option(
|
|
110
|
+
"--output-format",
|
|
111
|
+
type=click.Choice(DESCRIBE_OUTPUT_FORMATS, case_sensitive=False),
|
|
112
|
+
default="table",
|
|
113
|
+
show_default=True,
|
|
114
|
+
help="Output format. Use 'json' for machine-readable output.",
|
|
115
|
+
)
|
|
116
|
+
@click.argument("name", type=str)
|
|
117
|
+
@click.pass_context
|
|
118
|
+
@load_config
|
|
119
|
+
@validate_xsoar_connectivity
|
|
120
|
+
def describe(ctx: click.Context, environment: str | None, content_type: str, output_format: str, name: str) -> None:
|
|
121
|
+
"""Describe a single content item in detail.
|
|
122
|
+
|
|
123
|
+
Looks up one script, playbook, or command by name and shows its
|
|
124
|
+
description, arguments, and inputs/outputs. Commands also show the
|
|
125
|
+
integration brand and its configured instances. Use this after
|
|
126
|
+
'content list' to get the detail needed to actually use an item.
|
|
127
|
+
"""
|
|
128
|
+
config = get_xsoar_config(ctx)
|
|
129
|
+
xsoar_client: Client = config.get_client(environment)
|
|
130
|
+
raw = xsoar_client.content.describe(content_type, name)
|
|
131
|
+
if raw is None:
|
|
132
|
+
click.echo(f"Error: {content_type} '{name}' not found")
|
|
133
|
+
ctx.exit(1)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
item = describe_item(content_type, raw)
|
|
137
|
+
click.echo(format_describe(content_type, item, output_format=output_format))
|
|
86
138
|
|
|
87
139
|
|
|
88
140
|
@click.command()
|
|
@@ -175,4 +227,5 @@ def download(ctx: click.Context, environment: str | None, content_type: str, out
|
|
|
175
227
|
|
|
176
228
|
content.add_command(get_detached)
|
|
177
229
|
content.add_command(list_content)
|
|
230
|
+
content.add_command(describe)
|
|
178
231
|
content.add_command(download)
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""Helpers for filtering verbose XSOAR content responses.
|
|
2
|
+
|
|
3
|
+
The raw JSON returned by xsoar_client.content.list() contains far more detail
|
|
4
|
+
than downstream consumers (especially LLM-based tooling) need. The functions
|
|
5
|
+
in this module strip each content type down to the fields that matter while
|
|
6
|
+
keeping the output structure extensible.
|
|
7
|
+
|
|
8
|
+
``content list`` uses the ``summarize_*`` functions to produce a compact
|
|
9
|
+
id/comment summary per item, designed for discovery where a consumer scans
|
|
10
|
+
the list to identify relevant items.
|
|
11
|
+
|
|
12
|
+
The ``filter_*`` functions reduce each item to its actionable detail
|
|
13
|
+
(arguments, inputs, outputs). They are reserved for the ``content describe``
|
|
14
|
+
command, which presents a single item in depth.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
# -- Scripts -----------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def summarize_scripts(scripts: list[dict]) -> list[dict]:
|
|
23
|
+
"""Return an id + comment summary for each script.
|
|
24
|
+
|
|
25
|
+
This is the most compact representation, suitable for discovery queries
|
|
26
|
+
where the consumer only needs to identify scripts by name and purpose.
|
|
27
|
+
"""
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
"id": script.get("id", ""),
|
|
31
|
+
"comment": script.get("comment", ""),
|
|
32
|
+
}
|
|
33
|
+
for script in scripts
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def filter_scripts(scripts: list[dict]) -> list[dict]:
|
|
38
|
+
"""Return a detailed (but still filtered) representation of each script.
|
|
39
|
+
|
|
40
|
+
Keeps ``id``, ``comment``, and ``arguments`` (with each argument reduced
|
|
41
|
+
to ``name``, ``required``, ``deprecated``, ``description``).
|
|
42
|
+
Scripts whose ``arguments`` value is ``None`` get an empty list.
|
|
43
|
+
"""
|
|
44
|
+
argument_keys = ("name", "required", "deprecated", "description")
|
|
45
|
+
filtered: list[dict] = []
|
|
46
|
+
|
|
47
|
+
for script in scripts:
|
|
48
|
+
arguments = script.get("arguments") or []
|
|
49
|
+
filtered.append(
|
|
50
|
+
{
|
|
51
|
+
"id": script.get("id", ""),
|
|
52
|
+
"comment": script.get("comment", ""),
|
|
53
|
+
"arguments": [{key: arg.get(key) for key in argument_keys} for arg in arguments],
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return filtered
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def summarize_playbooks(playbooks: list[dict]) -> list[dict]:
|
|
61
|
+
"""Return an id + comment summary for each playbook.
|
|
62
|
+
|
|
63
|
+
The ``comment`` field holds the playbook description and matches the
|
|
64
|
+
field name used by scripts, keeping the structure consistent across
|
|
65
|
+
content types.
|
|
66
|
+
"""
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
"id": playbook.get("id", ""),
|
|
70
|
+
"comment": playbook.get("comment", ""),
|
|
71
|
+
}
|
|
72
|
+
for playbook in playbooks
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def filter_playbooks(playbooks: list[dict]) -> list[dict]:
|
|
77
|
+
"""Return a detailed (but still filtered) representation of each playbook.
|
|
78
|
+
|
|
79
|
+
Keeps ``id``, ``comment``, ``inputs`` (reduced to ``key`` and
|
|
80
|
+
``description``), and ``outputs`` (reduced to ``contextPath``,
|
|
81
|
+
``description``, ``type``). The ``tasks`` blob is excluded because it
|
|
82
|
+
dominates the response size and is not useful for deciding whether to
|
|
83
|
+
use a playbook.
|
|
84
|
+
"""
|
|
85
|
+
input_keys = ("key", "description")
|
|
86
|
+
output_keys = ("contextPath", "description", "type")
|
|
87
|
+
filtered: list[dict] = []
|
|
88
|
+
|
|
89
|
+
for playbook in playbooks:
|
|
90
|
+
inputs = playbook.get("inputs") or []
|
|
91
|
+
outputs = playbook.get("outputs") or []
|
|
92
|
+
filtered.append(
|
|
93
|
+
{
|
|
94
|
+
"id": playbook.get("id", ""),
|
|
95
|
+
"comment": playbook.get("comment", ""),
|
|
96
|
+
"inputs": [{key: inp.get(key) for key in input_keys} for inp in inputs],
|
|
97
|
+
"outputs": [{key: out.get(key) for key in output_keys} for out in outputs],
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return filtered
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _group_commands_by_brand(instances: list[dict]) -> list[dict]:
|
|
105
|
+
"""Deduplicate integration instances by brand.
|
|
106
|
+
|
|
107
|
+
The raw ``/user/commands`` response contains one entry per integration
|
|
108
|
+
instance. When the same brand is configured with multiple instances the
|
|
109
|
+
command definitions are identical. Grouping by brand removes this
|
|
110
|
+
duplication and keeps the output focused on what commands are available
|
|
111
|
+
rather than how many instances exist.
|
|
112
|
+
"""
|
|
113
|
+
seen: dict[str, dict] = {}
|
|
114
|
+
for instance in instances:
|
|
115
|
+
brand = instance.get("brand", "")
|
|
116
|
+
if brand not in seen:
|
|
117
|
+
seen[brand] = instance
|
|
118
|
+
return list(seen.values())
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def summarize_commands(instances: list[dict]) -> list[dict]:
|
|
122
|
+
"""Return a brand + command summary for each integration.
|
|
123
|
+
|
|
124
|
+
Deduplicates by brand and reduces each command to ``name`` and
|
|
125
|
+
``description``. Returning objects (rather than plain name strings)
|
|
126
|
+
keeps the structure consistent with ``filter_commands`` so formatters
|
|
127
|
+
do not need to handle both shapes.
|
|
128
|
+
"""
|
|
129
|
+
grouped = _group_commands_by_brand(instances)
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
"brand": instance.get("brand", ""),
|
|
133
|
+
"commands": [
|
|
134
|
+
{
|
|
135
|
+
"name": cmd.get("name", ""),
|
|
136
|
+
"description": cmd.get("description", ""),
|
|
137
|
+
}
|
|
138
|
+
for cmd in instance.get("commands") or []
|
|
139
|
+
],
|
|
140
|
+
}
|
|
141
|
+
for instance in grouped
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def filter_commands(instances: list[dict]) -> list[dict]:
|
|
146
|
+
"""Return a detailed (but still filtered) representation of each integration's commands.
|
|
147
|
+
|
|
148
|
+
Deduplicates by brand. Keeps each command's ``name``, ``description``,
|
|
149
|
+
``arguments`` (reduced to ``name``, ``required``, ``deprecated``,
|
|
150
|
+
``description``), and ``outputs`` (reduced to ``contextPath``,
|
|
151
|
+
``description``, ``type``).
|
|
152
|
+
"""
|
|
153
|
+
argument_keys = ("name", "required", "deprecated", "description")
|
|
154
|
+
output_keys = ("contextPath", "description", "type")
|
|
155
|
+
grouped = _group_commands_by_brand(instances)
|
|
156
|
+
filtered: list[dict] = []
|
|
157
|
+
|
|
158
|
+
for instance in grouped:
|
|
159
|
+
commands: list[dict] = []
|
|
160
|
+
for cmd in instance.get("commands") or []:
|
|
161
|
+
arguments = cmd.get("arguments") or []
|
|
162
|
+
outputs = cmd.get("outputs") or []
|
|
163
|
+
commands.append(
|
|
164
|
+
{
|
|
165
|
+
"name": cmd.get("name", ""),
|
|
166
|
+
"description": cmd.get("description", ""),
|
|
167
|
+
"arguments": [{key: arg.get(key) for key in argument_keys} for arg in arguments],
|
|
168
|
+
"outputs": [{key: out.get(key) for key in output_keys} for out in outputs],
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
filtered.append(
|
|
172
|
+
{
|
|
173
|
+
"brand": instance.get("brand", ""),
|
|
174
|
+
"commands": commands,
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return filtered
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# -- Detached content --------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def format_detached_summary(items: list[dict], content_type: str) -> str:
|
|
185
|
+
"""Format a human-readable summary of detached content items.
|
|
186
|
+
|
|
187
|
+
*items* is the list of content records returned for a single content type
|
|
188
|
+
(for example the ``scripts`` list from the detached automation search).
|
|
189
|
+
*content_type* is the plural label used in the output (for example
|
|
190
|
+
``"scripts"``).
|
|
191
|
+
|
|
192
|
+
When *items* is empty, returns ``"No detached <content_type> found"``.
|
|
193
|
+
Otherwise returns a header line with the count followed by one indented
|
|
194
|
+
line per item in the form ``<name> (ID: <id>)``.
|
|
195
|
+
"""
|
|
196
|
+
if not items:
|
|
197
|
+
return f"No detached {content_type} found"
|
|
198
|
+
|
|
199
|
+
lines = [f"Found {len(items)} detached {content_type}:"]
|
|
200
|
+
lines.extend(f" {item.get('name', '')} (ID: {item.get('id', '')})" for item in items)
|
|
201
|
+
return "\n".join(lines)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def filter_content(raw: dict) -> dict:
|
|
205
|
+
"""Produce a compact id/comment summary for each content type in *raw*.
|
|
206
|
+
|
|
207
|
+
``raw`` is the dict returned by ``xsoar_client.content.list()`` and may
|
|
208
|
+
contain any combination of ``scripts``, ``playbooks``, and ``commands``
|
|
209
|
+
keys depending on the requested type.
|
|
210
|
+
"""
|
|
211
|
+
result: dict = {}
|
|
212
|
+
|
|
213
|
+
if "scripts" in raw:
|
|
214
|
+
result["scripts"] = summarize_scripts(raw["scripts"])
|
|
215
|
+
|
|
216
|
+
if "playbooks" in raw:
|
|
217
|
+
result["playbooks"] = summarize_playbooks(raw["playbooks"])
|
|
218
|
+
|
|
219
|
+
if "commands" in raw:
|
|
220
|
+
result["commands"] = summarize_commands(raw["commands"])
|
|
221
|
+
|
|
222
|
+
return result
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
_SEARCH_FIELDS = ("id", "name", "comment", "description")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _item_matches(item: dict, term: str) -> bool:
|
|
229
|
+
"""Return True if *term* is a substring of any searchable field on *item*.
|
|
230
|
+
|
|
231
|
+
The match is case-insensitive and checks ``id``, ``name``, ``comment``,
|
|
232
|
+
and ``description`` (whichever are present). *term* is expected to be
|
|
233
|
+
already lowercased.
|
|
234
|
+
"""
|
|
235
|
+
return any(term in str(item.get(field, "")).lower() for field in _SEARCH_FIELDS)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def search_content(summary: dict, term: str) -> dict:
|
|
239
|
+
"""Filter a content summary down to items matching *term*.
|
|
240
|
+
|
|
241
|
+
*summary* is the dict produced by ``filter_content()``. The match is a
|
|
242
|
+
case-insensitive substring test against each item's ``id``, ``name``,
|
|
243
|
+
``comment``, and ``description`` fields (whichever are present).
|
|
244
|
+
|
|
245
|
+
For scripts and playbooks, non-matching items are dropped. For commands,
|
|
246
|
+
only matching commands are kept within each brand group, and brand groups
|
|
247
|
+
left with no matching commands are dropped. Content types left with no
|
|
248
|
+
matches are omitted from the result entirely.
|
|
249
|
+
|
|
250
|
+
When *term* is empty, the summary is returned unchanged.
|
|
251
|
+
"""
|
|
252
|
+
if not term:
|
|
253
|
+
return summary
|
|
254
|
+
|
|
255
|
+
needle = term.lower()
|
|
256
|
+
result: dict = {}
|
|
257
|
+
|
|
258
|
+
for content_type in ("scripts", "playbooks"):
|
|
259
|
+
items = summary.get(content_type)
|
|
260
|
+
if items is None:
|
|
261
|
+
continue
|
|
262
|
+
matches = [item for item in items if _item_matches(item, needle)]
|
|
263
|
+
if matches:
|
|
264
|
+
result[content_type] = matches
|
|
265
|
+
|
|
266
|
+
if "commands" in summary:
|
|
267
|
+
groups: list[dict] = []
|
|
268
|
+
for group in summary["commands"]:
|
|
269
|
+
matching = [cmd for cmd in group.get("commands", []) if _item_matches(cmd, needle)]
|
|
270
|
+
if matching:
|
|
271
|
+
groups.append({"brand": group.get("brand", ""), "commands": matching})
|
|
272
|
+
if groups:
|
|
273
|
+
result["commands"] = groups
|
|
274
|
+
|
|
275
|
+
return result
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def describe_item(item_type: str, raw: dict) -> dict:
|
|
279
|
+
"""Reduce a single raw content item to its actionable detail.
|
|
280
|
+
|
|
281
|
+
*item_type* is ``script``, ``playbook``, or ``command`` (singular).
|
|
282
|
+
*raw* is the record returned by ``Content.describe()``. For commands the
|
|
283
|
+
raw record wraps the command in ``{brand, instances, command}``; the
|
|
284
|
+
reduced result flattens that into the command fields plus ``brand`` and
|
|
285
|
+
``instances``.
|
|
286
|
+
"""
|
|
287
|
+
if item_type == "script":
|
|
288
|
+
return filter_scripts([raw])[0]
|
|
289
|
+
if item_type == "playbook":
|
|
290
|
+
return filter_playbooks([raw])[0]
|
|
291
|
+
if item_type == "command":
|
|
292
|
+
brand = raw.get("brand", "")
|
|
293
|
+
instances = raw.get("instances", [])
|
|
294
|
+
reduced = filter_commands([{"brand": brand, "commands": [raw.get("command", {})]}])[0]["commands"][0]
|
|
295
|
+
return {
|
|
296
|
+
"name": reduced["name"],
|
|
297
|
+
"brand": brand,
|
|
298
|
+
"instances": instances,
|
|
299
|
+
"description": reduced["description"],
|
|
300
|
+
"arguments": reduced["arguments"],
|
|
301
|
+
"outputs": reduced["outputs"],
|
|
302
|
+
}
|
|
303
|
+
raise ValueError(f"Invalid value {item_type=}")
|