xsoar-cli 2.2.0__tar.gz → 2.3.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 (171) hide show
  1. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/CHANGELOG.md +23 -0
  2. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/CLAUDE.md +6 -4
  3. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/PKG-INFO +1 -1
  4. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/_version.py +2 -2
  5. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/cli.py +40 -3
  6. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/content/README.md +2 -0
  7. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/content/commands.py +14 -8
  8. xsoar_cli-2.3.0/src/xsoar_cli/commands/execute/README.md +67 -0
  9. xsoar_cli-2.3.0/src/xsoar_cli/commands/execute/commands.py +337 -0
  10. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/manifest/README.md +4 -1
  11. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/manifest/commands.py +36 -35
  12. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/content.py +18 -18
  13. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/client.py +2 -0
  14. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/content.py +35 -9
  15. xsoar_cli-2.3.0/src/xsoar_cli/xsoar_client/execution.py +260 -0
  16. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/conftest.py +37 -0
  17. xsoar_cli-2.3.0/tests/cli/test_base.py +71 -0
  18. xsoar_cli-2.3.0/tests/cli/test_execute.py +287 -0
  19. xsoar_cli-2.3.0/tests/cli/test_manifest.py +225 -0
  20. xsoar_cli-2.3.0/tests/unit/__init__.py +0 -0
  21. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_content_domain.py +36 -5
  22. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_content_filters.py +7 -7
  23. xsoar_cli-2.3.0/tests/unit/test_execution.py +225 -0
  24. xsoar_cli-2.2.0/tests/cli/test_base.py +0 -17
  25. xsoar_cli-2.2.0/tests/cli/test_manifest.py +0 -18
  26. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.github/workflows/main.yml +0 -0
  27. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.github/workflows/pull-request-open.yml +0 -0
  28. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.github/workflows/release.yml +0 -0
  29. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.github/workflows/update-changelog.yml +0 -0
  30. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.gitignore +0 -0
  31. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/CONTRIBUTING.md +0 -0
  32. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/LICENSE.txt +0 -0
  33. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/README.md +0 -0
  34. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/pyproject.toml +0 -0
  35. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/__init__.py +0 -0
  36. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/__init__.py +0 -0
  37. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/case/README.md +0 -0
  38. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/case/__init__.py +0 -0
  39. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/case/commands.py +0 -0
  40. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/completions/README.md +0 -0
  41. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/completions/__init__.py +0 -0
  42. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/completions/commands.py +0 -0
  43. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/config/README.md +0 -0
  44. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/config/__init__.py +0 -0
  45. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/config/commands.py +0 -0
  46. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/content/__init__.py +0 -0
  47. {xsoar_cli-2.2.0/src/xsoar_cli/commands/graph → xsoar_cli-2.3.0/src/xsoar_cli/commands/execute}/__init__.py +0 -0
  48. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/graph/README.md +0 -0
  49. {xsoar_cli-2.2.0/src/xsoar_cli/commands/integration → xsoar_cli-2.3.0/src/xsoar_cli/commands/graph}/__init__.py +0 -0
  50. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/graph/commands.py +0 -0
  51. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/integration/README.md +0 -0
  52. {xsoar_cli-2.2.0/src/xsoar_cli/commands/manifest → xsoar_cli-2.3.0/src/xsoar_cli/commands/integration}/__init__.py +0 -0
  53. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/integration/commands.py +0 -0
  54. {xsoar_cli-2.2.0/src/xsoar_cli/commands/pack → xsoar_cli-2.3.0/src/xsoar_cli/commands/manifest}/__init__.py +0 -0
  55. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/pack/README.md +0 -0
  56. {xsoar_cli-2.2.0/src/xsoar_cli/commands/plugins → xsoar_cli-2.3.0/src/xsoar_cli/commands/pack}/__init__.py +0 -0
  57. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/pack/commands.py +0 -0
  58. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/plugins/README.md +0 -0
  59. {xsoar_cli-2.2.0/src/xsoar_cli/commands/rbac → xsoar_cli-2.3.0/src/xsoar_cli/commands/plugins}/__init__.py +0 -0
  60. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/plugins/commands.py +0 -0
  61. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/rbac/README.md +0 -0
  62. {xsoar_cli-2.2.0/src/xsoar_cli/error_handling → xsoar_cli-2.3.0/src/xsoar_cli/commands/rbac}/__init__.py +0 -0
  63. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/rbac/commands.py +0 -0
  64. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/configuration.py +0 -0
  65. {xsoar_cli-2.2.0/src/xsoar_cli/utilities → xsoar_cli-2.3.0/src/xsoar_cli/error_handling}/__init__.py +0 -0
  66. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/error_handling/connection.py +0 -0
  67. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/error_handling/http.py +0 -0
  68. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/log.py +0 -0
  69. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/plugins/README.md +0 -0
  70. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/plugins/__init__.py +0 -0
  71. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/plugins/manager.py +0 -0
  72. {xsoar_cli-2.2.0/src/xsoar_cli/xsoar_client → xsoar_cli-2.3.0/src/xsoar_cli/utilities}/__init__.py +0 -0
  73. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/config_file.py +0 -0
  74. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/download_content_handlers.py +0 -0
  75. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/manifest.py +0 -0
  76. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/validators.py +0 -0
  77. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/version_check.py +0 -0
  78. {xsoar_cli-2.2.0/src/xsoar_cli/xsoar_client/artifact_providers → xsoar_cli-2.3.0/src/xsoar_cli/xsoar_client}/__init__.py +0 -0
  79. {xsoar_cli-2.2.0/tests/cli → xsoar_cli-2.3.0/src/xsoar_cli/xsoar_client/artifact_providers}/__init__.py +0 -0
  80. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/artifact_providers/azure.py +0 -0
  81. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/artifact_providers/base.py +0 -0
  82. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/artifact_providers/s3.py +0 -0
  83. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/cases.py +0 -0
  84. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/constants.py +0 -0
  85. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/integrations.py +0 -0
  86. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/packs.py +0 -0
  87. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/rbac.py +0 -0
  88. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/__init__.py +0 -0
  89. {xsoar_cli-2.2.0/tests/unit → xsoar_cli-2.3.0/tests/cli}/__init__.py +0 -0
  90. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_case.py +0 -0
  91. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_completions.py +0 -0
  92. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_config.py +0 -0
  93. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_content.py +0 -0
  94. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_graph.py +0 -0
  95. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_pack.py +0 -0
  96. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_plugins.py +0 -0
  97. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/conftest.py +0 -0
  98. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/cases/create.json +0 -0
  99. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/cases/get.json +0 -0
  100. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/content/automation_search.json +0 -0
  101. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/content/playbook_search.json +0 -0
  102. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/content/user_commands.json +0 -0
  103. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/integrations/instances.json +0 -0
  104. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/manifest_base.json +0 -0
  105. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/manifest_invalid.json +0 -0
  106. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/manifest_with_pack_not_on_server.json +0 -0
  107. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/server_base_response.json +0 -0
  108. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/server_base_response_missing_one_pack.json +0 -0
  109. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/server_base_response_with_updates.json +0 -0
  110. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/server_base_response_with_updates_and_one_extra.json +0 -0
  111. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/packs/installed.json +0 -0
  112. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/packs/installed_expired.json +0 -0
  113. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/rbac/roles.json +0 -0
  114. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/rbac/user_groups.json +0 -0
  115. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/rbac/users.json +0 -0
  116. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Download/playbook-empty.yml +0 -0
  117. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/.pack-ignore +0 -0
  118. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/.secrets-ignore +0 -0
  119. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/Author_image.png +0 -0
  120. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/Playbooks/GenericPlaybook.yml +0 -0
  121. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/README.md +0 -0
  122. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/pack_metadata.json +0 -0
  123. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/.pack-ignore +0 -0
  124. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/.secrets-ignore +0 -0
  125. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Author_image.png +0 -0
  126. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/README.md +0 -0
  127. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.py +0 -0
  128. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.yml +0 -0
  129. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/README.md +0 -0
  130. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/pack_metadata.json +0 -0
  131. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/.pack-ignore +0 -0
  132. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/.secrets-ignore +0 -0
  133. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Author_image.png +0 -0
  134. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Playbooks/EDR_InitialTriage.yml +0 -0
  135. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/README.md +0 -0
  136. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.py +0 -0
  137. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.yml +0 -0
  138. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/README.md +0 -0
  139. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.py +0 -0
  140. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.yml +0 -0
  141. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/README.md +0 -0
  142. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.py +0 -0
  143. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.yml +0 -0
  144. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/README.md +0 -0
  145. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/test_data/basescript-dummy.json +0 -0
  146. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/pack_metadata.json +0 -0
  147. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/.pack-ignore +0 -0
  148. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/.secrets-ignore +0 -0
  149. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/Author_image.png +0 -0
  150. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/Layouts/layoutscontainer-GenericLayout.json +0 -0
  151. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/README.md +0 -0
  152. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/pack_metadata.json +0 -0
  153. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/Not_applicable/Playbooks/Empty.yml +0 -0
  154. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/conftest.py +0 -0
  155. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_artifact_azure.py +0 -0
  156. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_artifact_base.py +0 -0
  157. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_artifact_s3.py +0 -0
  158. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_cases.py +0 -0
  159. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_client.py +0 -0
  160. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_config_file.py +0 -0
  161. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_content_handlers.py +0 -0
  162. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_error_handling.py +0 -0
  163. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_integrations.py +0 -0
  164. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_main.py +0 -0
  165. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_manifest_utils.py +0 -0
  166. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_packs.py +0 -0
  167. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_plugin_manager.py +0 -0
  168. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_rbac.py +0 -0
  169. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_validators.py +0 -0
  170. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_version_check.py +0 -0
  171. {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/uv.lock +0 -0
@@ -8,6 +8,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
8
8
 
9
9
  ### Added
10
10
 
11
+ - `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
+ - `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
+
14
+ ### Fixed
15
+
16
+ - `content download` now correctly resolves playbooks whose ID differs from their name when the name contains spaces. Previously, the name search query was not quoted, causing it to return no results. In practice this affected custom playbooks created in the XSOAR UI, which use a UUID as their ID.
17
+
18
+ ### Changed
19
+
20
+ - **Breaking:** `manifest validate` now validates all packs by default (previously defaulted to `--mode diff`). Use the new `--only-changed` flag to validate only packs that differ from what is installed on the server.
21
+ - **Breaking:** `content list` now uses a single `--detail-level` option (choices: `short`, `extended`, `full`, default: `short`) instead of the separate `--details` and `--verbose` flags.
22
+ - Plugin commands are now listed under a separate `Plugins:` section in `--help` output instead of being mixed with core commands.
23
+
24
+ ### Removed
25
+
26
+ - `manifest validate --mode` option. Use `--only-changed` instead of `--mode diff`. Full validation is now the default.
27
+ - `content list --details` flag. Use `--detail-level extended` instead.
28
+ - `content list --verbose` flag. Use `--detail-level full` instead.
29
+
30
+ ## [2.2.0] - 2026-04-10
31
+
32
+ ### Added
33
+
11
34
  - `config set-version-check` command to enable or disable the version update check on CLI startup. Accepts `--enable` or `--disable`.
12
35
  - `plugins init` command to create the plugins directory and generate an example plugin.
13
36
 
@@ -28,7 +28,7 @@ for confirmation before executing any terminal commands as they may have consequ
28
28
  - **Build System**: Hatchling (`pyproject.toml`)
29
29
  - **Package Manager**: uv (`uv.lock`)
30
30
  - **Linting**: Ruff
31
- - **Formatting**: Black
31
+ - **Formatting**: Ruff formatter (>99.9% Black-compatible)
32
32
  - **Testing**: pytest, pytest-cov
33
33
  - **CI**: GitHub Actions (Python 3.10-3.14)
34
34
 
@@ -142,10 +142,10 @@ uv run pytest tests/cli/
142
142
  uv run pytest tests/unit/
143
143
 
144
144
  # Format code
145
- black src/ tests/
145
+ uv run ruff format src/ tests/
146
146
 
147
147
  # Lint
148
- ruff check src/ tests/
148
+ uv run ruff check src/ tests/
149
149
 
150
150
  # Run the CLI
151
151
  uv run xsoar-cli --help
@@ -206,9 +206,11 @@ The user's live configuration at `~/.config/xsoar-cli/config.json` contains API
206
206
 
207
207
  ## Changelog
208
208
 
209
+ **Every PR that changes user-facing behavior MUST include a `CHANGELOG.md` update. Do not treat this as optional or as a follow-up step. The changelog entry is part of the implementation, not separate from it.**
210
+
209
211
  - The project maintains a `CHANGELOG.md` in the repository root.
210
212
  - The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
211
- - All user-facing changes must be documented in `CHANGELOG.md`. This includes:
213
+ - Changes that require a changelog entry:
212
214
  - Adding, removing, or renaming commands, options, or arguments (`Added` / `Removed`)
213
215
  - Changes to default behavior or output format (`Changed`)
214
216
  - Bug fixes (`Fixed`)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xsoar-cli
3
- Version: 2.2.0
3
+ Version: 2.3.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.2.0'
22
- __version_tuple__ = version_tuple = (2, 2, 0)
21
+ __version__ = version = '2.3.0'
22
+ __version_tuple__ = version_tuple = (2, 3, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -17,6 +17,7 @@ from .commands.case import commands as case_commands
17
17
  from .commands.completions import commands as completions_commands
18
18
  from .commands.config import commands as config_commands
19
19
  from .commands.content import commands as content_commands
20
+ from .commands.execute import commands as execute_commands
20
21
  from .commands.graph import commands as graph_commands
21
22
  from .commands.integration import commands as integration_commands
22
23
  from .commands.manifest import commands as manifest_commands
@@ -30,12 +31,46 @@ from .utilities.version_check import check_for_update
30
31
 
31
32
 
32
33
  class XSOARCliGroup(click.Group):
33
- """Custom Click group that surfaces plugin load failures when a command is not found.
34
+ """Custom Click group with separate help sections for core and plugin commands.
34
35
 
35
- Without this, a failed plugin silently disappears and the user gets a generic
36
- "No such command" error with no indication that a plugin was involved.
36
+ Also surfaces plugin load failures when a command is not found, so the user
37
+ does not get a generic "No such command" error with no indication that a
38
+ plugin was involved.
37
39
  """
38
40
 
41
+ def __init__(self, *args, **kwargs):
42
+ super().__init__(*args, **kwargs)
43
+ self.core_commands: set[str] = set()
44
+
45
+ def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
46
+ """Format help output with core commands and plugin commands in separate sections."""
47
+ core_rows = []
48
+ plugin_rows = []
49
+
50
+ for subcommand in self.list_commands(ctx):
51
+ cmd = self.get_command(ctx, subcommand)
52
+ if cmd is None or cmd.hidden:
53
+ continue
54
+ target = core_rows if subcommand in self.core_commands else plugin_rows
55
+ target.append((subcommand, cmd))
56
+
57
+ if not core_rows and not plugin_rows:
58
+ return
59
+
60
+ all_names = core_rows + plugin_rows
61
+ limit = formatter.width - 6 - max(len(name) for name, _ in all_names)
62
+
63
+ def make_rows(commands):
64
+ return [(name, cmd.get_short_help_str(limit)) for name, cmd in commands]
65
+
66
+ if core_rows:
67
+ with formatter.section("Commands"):
68
+ formatter.write_dl(make_rows(core_rows))
69
+
70
+ if plugin_rows:
71
+ with formatter.section("Plugins"):
72
+ formatter.write_dl(make_rows(plugin_rows))
73
+
39
74
  def resolve_command(self, ctx: click.Context, args: list) -> tuple:
40
75
  try:
41
76
  return super().resolve_command(ctx, args)
@@ -70,6 +105,7 @@ def _register_commands() -> None:
70
105
  cli.add_command(config_commands.config)
71
106
  cli.add_command(content_commands.content)
72
107
 
108
+ cli.add_command(execute_commands.execute)
73
109
  cli.add_command(graph_commands.graph)
74
110
  cli.add_command(integration_commands.integration)
75
111
  cli.add_command(manifest_commands.manifest)
@@ -129,6 +165,7 @@ def _configure_logging(config_data: dict | None) -> LoggingSetup:
129
165
  # "Exit:" entries for pytest invocations.
130
166
  _register_commands()
131
167
  CORE_COMMANDS, plugin_manager = _load_plugins()
168
+ cli.core_commands = set(CORE_COMMANDS)
132
169
 
133
170
  # Initialized in main(). None when the module is imported without calling main(),
134
171
  # which is the case during test runs.
@@ -29,12 +29,14 @@ List available content items. Enumerates commands, playbooks and scripts availab
29
29
  **Options:**
30
30
  - `--environment TEXT` - Target environment (default: uses default environment from config)
31
31
  - `--type [scripts|playbooks|commands|all]` - Type of content items to list (default: all)
32
+ - `--detail-level [short|extended|full]` - Amount of detail in the output (default: short)
32
33
 
33
34
  **Examples:**
34
35
  ```
35
36
  xsoar-cli content list
36
37
  xsoar-cli content list --environment prod
37
38
  xsoar-cli content list --type commands
39
+ xsoar-cli content list --type commands --detail-level extended
38
40
  xsoar-cli content list --type playbooks --environment dev
39
41
  ```
40
42
 
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
7
7
  import click
8
8
 
9
9
  from xsoar_cli.utilities.config_file import get_xsoar_config, load_config
10
- from xsoar_cli.utilities.content import filter_content
10
+ from xsoar_cli.utilities.content import DETAIL_LEVELS, filter_content
11
11
  from xsoar_cli.utilities.download_content_handlers import HANDLERS, resolve_output_path
12
12
  from xsoar_cli.utilities.validators import validate_xsoar_connectivity
13
13
 
@@ -55,19 +55,25 @@ def get_detached(ctx: click.Context, environment: str | None, content_type: str)
55
55
  show_default=True,
56
56
  help="Type of content items to list.",
57
57
  )
58
- @click.option("--detail", is_flag=True, default=False, help="Include argument-level detail in filtered output.")
59
- @click.option("--verbose", is_flag=True, default=False, help="Output the full unfiltered response.")
58
+ @click.option(
59
+ "--detail-level",
60
+ type=click.Choice(DETAIL_LEVELS, case_sensitive=False),
61
+ default="short",
62
+ show_default=True,
63
+ help="Amount of detail to include in the output.",
64
+ )
60
65
  @click.pass_context
61
66
  @load_config
62
67
  @validate_xsoar_connectivity
63
- def list_content(ctx: click.Context, environment: str | None, content_type: str, detail: bool, verbose: bool) -> None:
68
+ def list_content(ctx: click.Context, environment: str | None, content_type: str, detail_level: str) -> None:
64
69
  """
65
- List detached content items. The purpose of this function is to list out available commands,
66
- playbooks and scripts, primarily to facilitate better AI generated playbooks"""
70
+ List available content items. Enumerates commands, playbooks and scripts
71
+ available on the server, primarily to facilitate better AI generated
72
+ playbooks."""
67
73
  config = get_xsoar_config(ctx)
68
74
  xsoar_client: Client = config.get_client(environment)
69
75
  json_blob = xsoar_client.content.list(content_type)
70
- if verbose:
76
+ if detail_level == "full":
71
77
  click.echo(json.dumps(json_blob, indent=4))
72
78
  ctx.exit(0)
73
79
 
@@ -75,7 +81,7 @@ def list_content(ctx: click.Context, environment: str | None, content_type: str,
75
81
  if isinstance(json_blob, list):
76
82
  json_blob = {content_type: json_blob}
77
83
 
78
- filtered = filter_content(json_blob, detail=detail)
84
+ filtered = filter_content(json_blob, detail_level=detail_level)
79
85
  click.echo(json.dumps(filtered, indent=4))
80
86
 
81
87
 
@@ -0,0 +1,67 @@
1
+ # Execute
2
+
3
+ Execute scripts, integration commands, and playbooks against an XSOAR investigation.
4
+
5
+ By default, execution happens in the user's playground. Pass `--case-id` to run against a specific case instead.
6
+
7
+ ## Command
8
+
9
+ Execute an automation script or integration command. There is no difference in how scripts and integration commands are invoked.
10
+
11
+ Use the bare command or script name, the same way you would reference it in a playbook. A leading `!` is accepted but optional.
12
+
13
+ Arguments are supplied as space-separated `key=value` pairs. Values containing whitespace are quoted automatically. A malformed argument (one without a `=`) causes the command to exit with an error.
14
+
15
+ **Shell note:** interactive shells (zsh, bash) treat characters like `!` and `|` specially. Prefer omitting the `!`, and wrap argument values in single quotes when they contain shell metacharacters, for example `query='index=zscaler | head 1'`.
16
+
17
+ **Syntax:** `xsoar-cli execute command [OPTIONS] NAME [ARGS]...`
18
+
19
+ **Options:**
20
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
21
+ - `--case-id INTEGER` - Case ID to execute against (default: the user's playground)
22
+ - `--mode [sync|async]` - Wait for results (`sync`) or submit and return the created entry (`async`) (default: "sync")
23
+ - `--timeout INTEGER` - Seconds to wait for results in sync mode before reporting the command is still running (default: 30)
24
+
25
+ **Arguments:**
26
+ - `NAME` - The script or integration command to run
27
+ - `ARGS` - Zero or more `key=value` argument pairs
28
+
29
+ **Output:**
30
+
31
+ On success, prints a completion line with the entry count, then for each resulting entry a short `Entry <id>:` header followed by two links: a War Room link (to view the entry in context) and an artifact viewer link (which offers a direct download of the entry content in XSOAR). The entry contents are not printed. If a command produces an error entry, the error contents are printed alongside its links and the command exits non-zero.
32
+
33
+ In sync mode, the command submits the request and then polls for the resulting War Room entries. If no results appear within `--timeout` seconds, the command is reported as still running with a War Room link to the submitted entry, and the exit code stays zero. A failed submission surfaces immediately as an error with a non-zero exit code.
34
+
35
+ **Examples:**
36
+ ```
37
+ xsoar-cli execute command MyScript arg1=val1 arg2=val2
38
+ xsoar-cli execute command whois query=example.com --case-id 12345
39
+ xsoar-cli execute command splunk-search query='index=zscaler | head 1'
40
+ xsoar-cli execute command MyScript arg1=val1 --mode async
41
+ xsoar-cli execute command LongRunningScript --timeout 60
42
+ ```
43
+
44
+ ## Playbook
45
+
46
+ Start a playbook in the playground or a specific case. The playbook name is resolved to its ID before starting, so the display name works even for custom playbooks whose ID is a UUID.
47
+
48
+ This is fire-and-forget: the playbook is started via the `setPlaybook` command and the CLI returns without waiting for the playbook to finish. Follow up in the XSOAR War Room to see its progress.
49
+
50
+ **Syntax:** `xsoar-cli execute playbook [OPTIONS] NAME`
51
+
52
+ **Options:**
53
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
54
+ - `--case-id INTEGER` - Case ID to start the playbook in (default: the user's playground)
55
+
56
+ **Arguments:**
57
+ - `NAME` - The playbook to start
58
+
59
+ **Output:**
60
+
61
+ Prints `Started playbook <name> in playground` or `Started playbook <name> in case <id>`. If the playbook name cannot be found, the command exits non-zero with an error.
62
+
63
+ **Examples:**
64
+ ```
65
+ xsoar-cli execute playbook "My Playbook"
66
+ xsoar-cli execute playbook "My Playbook" --case-id 12345
67
+ ```
@@ -0,0 +1,337 @@
1
+ import logging
2
+ from typing import TYPE_CHECKING
3
+
4
+ import click
5
+
6
+ from xsoar_cli.utilities.config_file import get_xsoar_config, load_config
7
+ from xsoar_cli.utilities.validators import validate_xsoar_connectivity
8
+ from xsoar_cli.xsoar_client.execution import DEFAULT_SYNC_TIMEOUT, EXECUTION_MODES
9
+
10
+ if TYPE_CHECKING:
11
+ from xsoar_cli.xsoar_client.client import Client
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # XSOAR entry type identifying an error entry in command output.
16
+ ERROR_ENTRY_TYPE = 4
17
+
18
+
19
+ def parse_arg_tokens(tokens: tuple[str, ...]) -> dict[str, str]:
20
+ """Parse variadic key=value tokens into a dictionary.
21
+
22
+ Raises ValueError on any token that does not contain a "=" separator.
23
+ """
24
+ args: dict[str, str] = {}
25
+ for token in tokens:
26
+ if "=" not in token:
27
+ msg = f"Malformed argument '{token}'. Arguments must be on the form key=value."
28
+ raise ValueError(msg)
29
+ key, value = token.split("=", 1)
30
+ args[key.strip()] = value.strip()
31
+ return args
32
+
33
+
34
+ def resolve_investigation_id(ctx: click.Context, xsoar_client: "Client", case_id: int | None) -> str:
35
+ """Return the target investigation ID.
36
+
37
+ Uses the supplied case ID when present, otherwise resolves the user's
38
+ playground investigation ID.
39
+
40
+ Distinguishes two failure modes when resolving the playground:
41
+
42
+ * ApiException means the XSOAR API call itself failed (connectivity, auth,
43
+ server error). The user is pointed at the environment.
44
+ * RuntimeError means the calls succeeded but no single playground could be
45
+ identified. The specific reason is surfaced to the user.
46
+
47
+ Both cases print a message and exit with a non-zero status.
48
+ """
49
+ # Lazy import for performance reasons
50
+ from demisto_client.demisto_api.rest import ApiException
51
+
52
+ if case_id is not None:
53
+ logger.debug("Using supplied case ID %d as investigation ID", case_id)
54
+ return str(case_id)
55
+
56
+ logger.debug("No case ID supplied, resolving user's playground investigation ID")
57
+ try:
58
+ investigation_id = xsoar_client.execution.resolve_playground_id()
59
+ except ApiException as ex:
60
+ logger.info("Playground lookup failed with API error: %s", ex)
61
+ click.echo(f"Error: failed to query XSOAR for the playground investigation: {ex}")
62
+ ctx.exit(1)
63
+ except RuntimeError as ex:
64
+ logger.info("Could not resolve playground investigation: %s", ex)
65
+ click.echo(f"Error: {ex}")
66
+ ctx.exit(1)
67
+ logger.debug("Resolved playground investigation ID: %s", investigation_id)
68
+ return investigation_id
69
+
70
+
71
+ def build_entry_url(server_url: str, view: str, segment: str, entry_id: str) -> str:
72
+ """Build a direct link to an entry in a given XSOAR view.
73
+
74
+ The URL shape is <server_url>/#/<view>/<segment>/<entry_id>, where view is
75
+ "WarRoom" or "artifactViewer" and segment is the literal "playground" for
76
+ playground executions or the case ID for a case. The server URL's trailing
77
+ slash, if any, is stripped.
78
+ """
79
+ base = server_url.rstrip("/")
80
+ return f"{base}/#/{view}/{segment}/{entry_id}"
81
+
82
+
83
+ def _result_entry_lines(entry_id: str, server_url: str, segment: str) -> list[str]:
84
+ """Build the grouped output lines for a single result entry.
85
+
86
+ Each result entry is shown with a short header and two links: the War Room
87
+ view for context and the artifact viewer for downloading the entry content.
88
+ The short entry number (the part before "@") is used in the header for
89
+ readability; the full id is preserved in the URLs.
90
+ """
91
+ short_id = entry_id.split("@", 1)[0]
92
+ return [
93
+ f"Entry {short_id}:",
94
+ f" War Room entry: {build_entry_url(server_url, 'WarRoom', segment, entry_id)}",
95
+ f" Artifact viewer: {build_entry_url(server_url, 'artifactViewer', segment, entry_id)}",
96
+ ]
97
+
98
+
99
+ def render_command_output(
100
+ result: dict,
101
+ name: str,
102
+ server_url: str,
103
+ warroom_segment: str,
104
+ ) -> tuple[str, bool]:
105
+ """Render an execute-command result for display.
106
+
107
+ Returns a (text, had_error) tuple. had_error is True when the command
108
+ returned one or more error entries, so the caller can exit non-zero.
109
+
110
+ Reports only what is known:
111
+
112
+ * sync success: that the command completed, the entry count, and War Room
113
+ and artifact viewer links per returned entry.
114
+ * sync error: the contents of each error entry and links to it.
115
+ * sync timeout: that no results arrived in time and the command may still
116
+ be running, with a War Room link to the submitted entry.
117
+ * async: that the command was submitted, with the created entry ID and a
118
+ War Room link.
119
+ """
120
+ if "entries" in result:
121
+ if result.get("timed_out"):
122
+ return _render_timeout_summary(result, server_url, warroom_segment), False
123
+ return _render_sync_summary(result["entries"], name, server_url, warroom_segment)
124
+ return _render_async_summary(result.get("entry", {}), name, server_url, warroom_segment), False
125
+
126
+
127
+ def _render_sync_summary(entries: list[dict], name: str, server_url: str, warroom_segment: str) -> tuple[str, bool]:
128
+ """Render the summary for a synchronous (blocking) command execution.
129
+
130
+ Each result entry is shown grouped with its War Room and artifact viewer
131
+ links, separated by a blank line for readability.
132
+ """
133
+ error_entries = [entry for entry in entries if entry.get("type") == ERROR_ENTRY_TYPE]
134
+
135
+ if error_entries:
136
+ total = len(entries)
137
+ error_count = len(error_entries)
138
+ total_plural = "entry" if total == 1 else "entries"
139
+ error_plural = "error" if error_count == 1 else "errors"
140
+ lines = [f"Command {name} completed with errors ({total} {total_plural}, {error_count} {error_plural})."]
141
+ for entry in error_entries:
142
+ entry_id = entry.get("id", "")
143
+ if not entry_id:
144
+ continue
145
+ lines.append("")
146
+ contents = entry.get("contents")
147
+ if contents:
148
+ lines.append(str(contents))
149
+ lines.extend(_result_entry_lines(entry_id, server_url, warroom_segment))
150
+ return "\n".join(lines), True
151
+
152
+ count = len(entries)
153
+ plural = "entry" if count == 1 else "entries"
154
+ lines = [f"Command {name} completed ({count} {plural})."]
155
+ for entry in entries:
156
+ entry_id = entry.get("id", "")
157
+ if not entry_id:
158
+ continue
159
+ lines.append("")
160
+ lines.extend(_result_entry_lines(entry_id, server_url, warroom_segment))
161
+ return "\n".join(lines), False
162
+
163
+
164
+ def _render_timeout_summary(result: dict, server_url: str, warroom_segment: str) -> str:
165
+ """Render the summary when a sync command produced no results before timeout.
166
+
167
+ A timeout is treated as success with a warning: the command was submitted
168
+ successfully (a submit failure surfaces fast as an API error), so it is
169
+ almost certainly still running. The link points at the submitted entry.
170
+ """
171
+ entry_id = result.get("entry_id", "")
172
+ lines = ["No command results within timeout. It may still be running."]
173
+ if entry_id:
174
+ lines.append(f"War Room entry: {build_entry_url(server_url, 'WarRoom', warroom_segment, entry_id)}")
175
+ return "\n".join(lines)
176
+
177
+
178
+ def _render_async_summary(entry: dict, name: str, server_url: str, warroom_segment: str) -> str:
179
+ """Render the summary for an asynchronous command submission."""
180
+ entry_id = entry.get("id", "")
181
+ lines = [f"Command {name} submitted. Entry ID: {entry_id}"]
182
+ if entry_id:
183
+ lines.append(f"War Room entry: {build_entry_url(server_url, 'WarRoom', warroom_segment, entry_id)}")
184
+ return "\n".join(lines)
185
+
186
+
187
+ @click.group()
188
+ def execute() -> None:
189
+ """Execute scripts, integration commands, and playbooks"""
190
+ pass
191
+
192
+
193
+ @click.command()
194
+ @click.option("--environment", default=None, help="Default environment set in config file.")
195
+ @click.option("--case-id", type=int, default=None, help="Case ID to execute against. Defaults to the user's playground.")
196
+ @click.option(
197
+ "--mode",
198
+ type=click.Choice(EXECUTION_MODES, case_sensitive=False),
199
+ default="sync",
200
+ show_default=True,
201
+ help="Execute synchronously (wait for and return results) or asynchronously (submit and return the entry).",
202
+ )
203
+ @click.option(
204
+ "--timeout",
205
+ type=int,
206
+ default=DEFAULT_SYNC_TIMEOUT,
207
+ show_default=True,
208
+ help="Seconds to wait for results in sync mode before reporting the command is still running.",
209
+ )
210
+ @click.argument("name", type=str)
211
+ @click.argument("args", nargs=-1, type=str)
212
+ @click.pass_context
213
+ @load_config
214
+ @validate_xsoar_connectivity
215
+ def command( # noqa: PLR0913
216
+ ctx: click.Context,
217
+ environment: str | None,
218
+ case_id: int | None,
219
+ mode: str,
220
+ timeout: int,
221
+ name: str,
222
+ args: tuple[str, ...],
223
+ ) -> None:
224
+ """Execute an automation script or integration command.
225
+
226
+ NAME is the script or integration command to run. There is no difference in
227
+ how scripts and integration commands are invoked. Use the bare name (as you
228
+ would reference it in a playbook). A leading "!" is accepted but optional.
229
+
230
+ Arguments are supplied as space-separated key=value pairs. Values containing
231
+ whitespace are quoted automatically.
232
+
233
+ Shell note: interactive shells treat characters like "!" and "|" specially.
234
+ Prefer omitting the "!" and wrap argument values in single quotes when they
235
+ contain shell metacharacters, for example query='index=foo | head 1'.
236
+
237
+ By default execution happens in the user's playground. Pass --case-id to run
238
+ against a specific case instead.
239
+
240
+ --mode sync waits for the command to finish and returns the resulting War
241
+ Room entries. --mode async submits the command and returns immediately with
242
+ the created entry.
243
+
244
+ In sync mode, --timeout bounds how long to wait for results. If the command
245
+ has not produced results by then, it is reported as still running and a War
246
+ Room link is printed; this is not treated as an error.
247
+
248
+ Usage examples:
249
+
250
+ xsoar-cli execute command MyScript arg1=val1 arg2=val2
251
+
252
+ xsoar-cli execute command whois query=example.com --case-id 12345
253
+
254
+ xsoar-cli execute command splunk-search query='index=zscaler | head 1'
255
+
256
+ xsoar-cli execute command MyScript arg1=val1 --mode async
257
+ """
258
+ # Lazy import for performance reasons
259
+ from demisto_client.demisto_api.rest import ApiException
260
+
261
+ try:
262
+ parsed_args = parse_arg_tokens(args)
263
+ except ValueError as ex:
264
+ click.echo(f"Error: {ex}")
265
+ ctx.exit(1)
266
+
267
+ config = get_xsoar_config(ctx)
268
+ xsoar_client: Client = config.get_client(environment)
269
+ investigation_id = resolve_investigation_id(ctx, xsoar_client, case_id)
270
+ # The War Room URL uses the literal "playground" segment for playground
271
+ # executions, and the case ID when running against a specific case.
272
+ warroom_segment = str(case_id) if case_id is not None else "playground"
273
+ # The leading "!" is optional on input; strip it for display so user-facing
274
+ # messages read the same whether or not the user included it.
275
+ display_name = name[1:] if name.startswith("!") else name
276
+ logger.info("Executing command '%s' (mode=%s) against investigation '%s'", name, mode, investigation_id)
277
+ if mode == "sync":
278
+ click.echo(f"Executing {display_name}, waiting up to {timeout}s for results...", err=True)
279
+ try:
280
+ result = xsoar_client.execution.execute_command(name, parsed_args, investigation_id, mode=mode, timeout=timeout)
281
+ except ApiException as ex:
282
+ logger.info("Command execution failed with API error: %s", ex)
283
+ click.echo(f"Error: command execution failed: {ex}")
284
+ ctx.exit(1)
285
+ output, had_error = render_command_output(result, display_name, xsoar_client.server_url, warroom_segment)
286
+ click.echo(output)
287
+ if had_error:
288
+ ctx.exit(1)
289
+
290
+
291
+ @click.command()
292
+ @click.option("--environment", default=None, help="Default environment set in config file.")
293
+ @click.option("--case-id", type=int, default=None, help="Case ID to execute against. Defaults to the user's playground.")
294
+ @click.argument("name", type=str)
295
+ @click.pass_context
296
+ @load_config
297
+ @validate_xsoar_connectivity
298
+ def playbook(ctx: click.Context, environment: str | None, case_id: int | None, name: str) -> None:
299
+ """Start a playbook.
300
+
301
+ NAME is the playbook to run.
302
+
303
+ By default the playbook is started in the user's playground. Pass --case-id
304
+ to start it in a specific case instead.
305
+
306
+ This is fire-and-forget: the playbook is started and the command returns
307
+ without waiting for it to finish.
308
+
309
+ Usage examples:
310
+
311
+ xsoar-cli execute playbook "My Playbook"
312
+
313
+ xsoar-cli execute playbook "My Playbook" --case-id 12345
314
+ """
315
+ # Lazy import for performance reasons
316
+ from demisto_client.demisto_api.rest import ApiException
317
+
318
+ config = get_xsoar_config(ctx)
319
+ xsoar_client: Client = config.get_client(environment)
320
+ investigation_id = resolve_investigation_id(ctx, xsoar_client, case_id)
321
+ target = f"case {case_id}" if case_id is not None else "playground"
322
+ logger.info("Starting playbook '%s' in %s (investigation '%s')", name, target, investigation_id)
323
+ try:
324
+ xsoar_client.execution.execute_playbook(name, investigation_id)
325
+ except ValueError as ex:
326
+ logger.info("Could not start playbook: %s", ex)
327
+ click.echo(f"Error: {ex}", err=True)
328
+ ctx.exit(1)
329
+ except ApiException as ex:
330
+ logger.info("Playbook start failed with API error: %s", ex)
331
+ click.echo(f"Error: failed to start playbook: {ex}", err=True)
332
+ ctx.exit(1)
333
+ click.echo(f"Started playbook {name} in {target}")
334
+
335
+
336
+ execute.add_command(command)
337
+ execute.add_command(playbook)
@@ -23,11 +23,13 @@ xsoar-cli manifest generate --environment prod
23
23
 
24
24
  Validate manifest JSON syntax and verify all specified content packs are available. Tests connectivity to pack sources and checks local pack metadata for development packs.
25
25
 
26
+ By default, all packs in the manifest are validated. Use `--only-changed` to limit validation to packs whose version differs from what is currently installed on the server. This is useful in CI pipelines where only a few packs change per PR.
27
+
26
28
  **Syntax:** `xsoar-cli manifest validate [OPTIONS] MANIFEST_PATH`
27
29
 
28
30
  **Options:**
29
31
  - `--environment TEXT` - Target environment (default: uses default environment from config)
30
- - `--mode [full|diff]` - Validate the full manifest, or only the definitions that diff with installed versions (default: diff)
32
+ - `--only-changed` - Only validate packs that differ from what is currently installed on the server
31
33
 
32
34
  **Arguments:**
33
35
  - `MANIFEST_PATH` - Path to the manifest file to validate
@@ -35,6 +37,7 @@ Validate manifest JSON syntax and verify all specified content packs are availab
35
37
  **Examples:**
36
38
  ```
37
39
  xsoar-cli manifest validate ./xsoar_config.json
40
+ xsoar-cli manifest validate --only-changed ./xsoar_config.json
38
41
  xsoar-cli manifest validate --environment staging ./xsoar_config.json
39
42
  ```
40
43