MagnetAutomate

Magnet Automate is an orchestration and automation platform that accelerates your digital forensics investigations by automating workflows and integrating with various forensic tools.

Forensics & Malware Analysis · Magnet Forensics

Details

IDMagnetAutomate
ProviderMagnet Forensics
CategoryForensics & Malware Analysis
From Version6.10.0
Docker Imagedemisto/fastapi:0.125.0.11206988

README

Magnet Automate is an orchestration and automation platform that accelerates your digital forensics investigations by automating workflows and integrating with various forensic tools.
This integration was integrated and tested with version 0.2.0 of Magnet Automate API.

Configure Magnet Automate in Cortex

Parameter Required
Server URL True
API Key True
Trust any certificate (not secure) False
Use system proxy settings False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

mf-automate-custom-fields-list


Retrieves a list of custom fields for cases and evidence sources.

Base Command

mf-automate-custom-fields-list

Input

Argument Name Description Required
limit The maximum number of results to return. Default value is 50. Default is 50. Optional
all_results Whether to return all of the results. Overrides the limit argument if used. Default value is False. Optional

Context Output

Path Type Description
MagnetAutomate.CustomFields.id Number The unique identifier of the custom field.
MagnetAutomate.CustomFields.name String The name of the custom field.
MagnetAutomate.CustomFields.type String The data type of the custom field.
MagnetAutomate.CustomFields.elementType Unknown The element type of the custom field.
MagnetAutomate.CustomFields.description String A description of the custom field.
MagnetAutomate.CustomFields.required Boolean Whether the custom field is required.
MagnetAutomate.CustomFields.exposeInWorkflow Boolean Whether the custom field is exposed in workflows.
MagnetAutomate.CustomFields.variableName String The variable name associated with the custom field.

Command Example

!mf-automate-custom-fields-list limit=10

mf-automate-case-create


Creates a new case in Magnet Automate. Use mf-automate-custom-fields-list to get all available custom fields.

Base Command

mf-automate-case-create

Input

Argument Name Description Required
case_number The unique case number to assign to the new case. Required
custom_field_values A JSON object containing custom field values for the case. Optional

Context Output

Path Type Description
MagnetAutomate.Case.id Number The unique identifier of the created case.
MagnetAutomate.Case.caseNumber String The case number assigned to the case.
MagnetAutomate.Case.customFieldValues Unknown The custom field values associated with the case.

Command Example

!mf-automate-case-create case_number="CASE-2024-001" custom_field_values="{\"2\": \"Civil\", \"3\": \"Magnet Forensics\"}"

mf-automate-cases-list


Retrieves a list of all cases or information about a specific case.

Base Command

mf-automate-cases-list

Input

Argument Name Description Required
case_id The unique identifier of the case to retrieve. Optional
limit The maximum number of results to return. Default value is 50. Default is 50. Optional
all_results Whether to return all of the results. Overrides the limit argument if used. Default value is False. Optional

Context Output

Path Type Description
MagnetAutomate.Case.id Number The unique identifier of the case.
MagnetAutomate.Case.caseNumber String The case number.

Command Example

!mf-automate-cases-list limit=5

Command Example

!mf-automate-cases-list case_id=10

mf-automate-case-delete


Deletes a specific case from Magnet Automate.

Base Command

mf-automate-case-delete

Input

Argument Name Description Required
case_id The unique identifier of the case to delete. Required

Context Output

There is no context output for this command.

Command Example

!mf-automate-case-delete case_id=10

mf-automate-case-cancel


Cancels an ongoing case in Magnet Automate.

Base Command

mf-automate-case-cancel

Input

Argument Name Description Required
case_id The unique identifier of the case to cancel. Required

Context Output

There is no context output for this command.

Command Example

!mf-automate-case-cancel case_id=10

mf-automate-workflow-run-start


Starts a new workflow run and associates it with a specific case.

Base Command

mf-automate-workflow-run-start

Input

Argument Name Description Required
case_id The unique identifier of the case to associate the workflow run with. Required
evidence_number An evidence number to apply to the evidence source. Required
type A JSON object defining the evidence type and its parameters (e.g., ImageSource path). Required
workflow_id The unique identifier of the workflow to run. Required
output_path The directory path where the workflow output will be stored. Optional
platform The platform associated with the evidence. Optional
decryption_type The type of decryption to use (e.g., Password, RecoveryKey). Optional
decryption_value The decryption key or password. Optional
continue_on_decryption_fail Whether to continue the workflow if decryption fails. Optional
custom_field_values A JSON object containing custom field values for the workflow run. Optional
assigned_node_name The name of the specific node to assign the workflow run to. Optional

Context Output

Path Type Description
MagnetAutomate.WorkflowRun.id Number The unique identifier of the started workflow run.
MagnetAutomate.WorkflowRun.path String The path to the workflow run data.
MagnetAutomate.WorkflowRun.version Number The version of the workflow run.
MagnetAutomate.WorkflowRun.caseId Number The identifier of the associated case.
MagnetAutomate.WorkflowRun.caseTypeId Number The identifier of the case type.
MagnetAutomate.WorkflowRun.basePath String The base path for the workflow run.
MagnetAutomate.WorkflowRun.automateVersion String The version of Magnet Automate used for the run.

Command Example

!mf-automate-workflow-run-start case_id=10 evidence_number="ExhibitA" type="{\"ImageSource\": {\"path\": \"C:\\\\testdata\\\\image\\\\image123.001\"}}" workflow_id=3 output_path="C:\\testdata\\output" platform="windows" decryption_type="password" decryption_value="MySecretPassword" continue_on_decryption_fail=false custom_field_values="{\"5\": \"Evidence Value A\", \"7\": \"Evidence Value B\"}" assigned_node_name="AGENT1"

mf-automate-workflow-run-list


Retrieves a list of all workflow runs for a specific case or details of a specific run.

Base Command

mf-automate-workflow-run-list

Input

Argument Name Description Required
case_id The unique identifier of the case to retrieve workflow runs for. Required
run_id The unique identifier of a specific workflow run to retrieve. Optional
limit The maximum number of results to return. Default value is 50. Default is 50. Optional
all_results Whether to return all of the results. Overrides the limit argument if used. Default value is False. Optional

Context Output

Path Type Description
MagnetAutomate.WorkflowRun.id Number The unique identifier of the workflow run.
MagnetAutomate.WorkflowRun.evidence Unknown Details about the evidence associated with the run.
MagnetAutomate.WorkflowRun.status String The current status of the workflow run.
MagnetAutomate.WorkflowRun.workflowId Number The identifier of the workflow being run.
MagnetAutomate.WorkflowRun.currentStage Unknown Information about the current stage of the workflow run.
MagnetAutomate.WorkflowRun.outputPath String The output path for the workflow run.
MagnetAutomate.WorkflowRun.startDateTime Date The date and time when the workflow run started.
MagnetAutomate.WorkflowRun.endDateTime Date The date and time when the workflow run ended.
MagnetAutomate.WorkflowRun.automateVersion String The version of Magnet Automate used.
MagnetAutomate.WorkflowRun.createdBy Unknown Information about the user who created the workflow run.
MagnetAutomate.WorkflowRun.duration Number The duration of the workflow run in seconds.
MagnetAutomate.WorkflowRun.completedStages Unknown A list of completed stages in the workflow run.

Command Example

!mf-automate-workflow-run-list case_id=10

Command Example

!mf-automate-workflow-run-list case_id=10 run_id=11

mf-automate-workflow-run-delete


Deletes a specific workflow run from a case.

Base Command

mf-automate-workflow-run-delete

Input

Argument Name Description Required
case_id The unique identifier of the case. Required
run_id The unique identifier of the workflow run to delete. Required

Context Output

There is no context output for this command.

Command Example

!mf-automate-workflow-run-delete case_id=10 run_id=11

mf-automate-workflow-run-cancel


Cancels a specific workflow run.

Base Command

mf-automate-workflow-run-cancel

Input

Argument Name Description Required
case_id The unique identifier of the case. Required
run_id The unique identifier of the workflow run to cancel. Required

Context Output

There is no context output for this command.

Command Example

!mf-automate-workflow-run-cancel case_id=10 run_id=11

mf-automate-merge-workflow-run-start


Starts a merge workflow run for multiple existing workflow runs.

Base Command

mf-automate-merge-workflow-run-start

Input

Argument Name Description Required
case_id The unique identifier of the case. Required
run_ids A list of workflow run identifiers to merge. Required
workflow_id The unique identifier of the merge workflow to run. Required
output_path The directory path where the merged output will be stored. Optional
assigned_node_name The name of the specific node to assign the merge workflow run to. Optional

Context Output

Path Type Description
MagnetAutomate.WorkflowRun.id Number The unique identifier of the started merge workflow run.
MagnetAutomate.WorkflowRun.path String The path to the merge workflow run data.
MagnetAutomate.WorkflowRun.version Number The version of the workflow run.
MagnetAutomate.WorkflowRun.caseId Number The identifier of the associated case.
MagnetAutomate.WorkflowRun.caseTypeId Number The identifier of the case type.
MagnetAutomate.WorkflowRun.basePath String The base path for the workflow run.
MagnetAutomate.WorkflowRun.automateVersion String The version of Magnet Automate used.

Command Example

!mf-automate-merge-workflow-run-start case_id=10 run_ids="1,2,3" workflow_id=3 output_path="C:\\testdata\\output" assigned_node_name="AGENT1"

mf-automate-workflow-list


Retrieves a list of all available workflows.

Base Command

mf-automate-workflow-list

Input

Argument Name Description Required
limit The maximum number of results to return. Default value is 50. Default is 50. Optional
all_results Whether to return all of the results. Overrides the limit argument if used. Default value is False. Optional

Context Output

Path Type Description
MagnetAutomate.Workflow.id Number The unique identifier of the workflow.
MagnetAutomate.Workflow.name String The name of the workflow.
MagnetAutomate.Workflow.type.name String The name of the workflow type.
MagnetAutomate.Workflow.description String A description of the workflow.
MagnetAutomate.Workflow.outputPath String The default output path for the workflow.

Command Example

!mf-automate-workflow-list limit=10

mf-automate-workflow-delete


Deletes a specific workflow from Magnet Automate.

Base Command

mf-automate-workflow-delete

Input

Argument Name Description Required
workflow_id The unique identifier of the workflow to delete. Required

Context Output

There is no context output for this command.

Command Example

!mf-automate-workflow-delete workflow_id=5

mf-automate-workflow-get


Retrieves detailed information and export data for a specific workflow. Use mf-automate-workflow-list to get all available workflows.

Base Command

mf-automate-workflow-get

Input

Argument Name Description Required
workflow_id The unique identifier of the workflow to retrieve. Required

Context Output

Path Type Description
MagnetAutomate.Workflow.id Number The unique identifier of the workflow.
MagnetAutomate.Workflow.automateVersion String The version of Magnet Automate the workflow was created in.
MagnetAutomate.Workflow.name String The name of the workflow.
MagnetAutomate.Workflow.description String A description of the workflow.
MagnetAutomate.Workflow.sourceType String The type of evidence source the workflow accepts.
MagnetAutomate.Workflow.sourceConfig Unknown Configuration details for the evidence source.
MagnetAutomate.Workflow.outputPath String The output path for the workflow.
MagnetAutomate.Workflow.keylistPath String The path to the keylist used by the workflow.
MagnetAutomate.Workflow.passwordListPath String The path to the password list used by the workflow.
MagnetAutomate.Workflow.continueOnDecryptionFail String Whether the workflow continues if decryption fails.
MagnetAutomate.Workflow.distribution String The distribution settings for the workflow.
MagnetAutomate.Workflow.localMode Boolean Whether the workflow runs in local mode.
MagnetAutomate.Workflow.timeExported Date The date and time when the workflow was exported.

Command Example

!mf-automate-workflow-get workflow_id=5

mf-automate-node-create


Creates a new node (agent) in Magnet Automate.

Base Command

mf-automate-node-create

Input

Argument Name Description Required
name The name of the node. Optional
address The network address or hostname of the node. Optional
working_directory The working directory for the node. Optional
applications_json A JSON array of applications installed on the node. Optional

Context Output

Path Type Description
MagnetAutomate.Node.id Number The unique identifier of the created node.
MagnetAutomate.Node.name String The name of the node.
MagnetAutomate.Node.status String The current status of the node.
MagnetAutomate.Node.workingDirectory String The working directory of the node.
MagnetAutomate.Node.address String The network address of the node.
MagnetAutomate.Node.applications Unknown A list of applications installed on the node.

Command Example

!mf-automate-node-create name="NODE-002" address="automate-node-2" working_directory="C:\\automate\\temp" applications_json="[{\"applicationName\": \"AXIOM Process\", \"applicationVersion\": \"7.0.0\", \"applicationPath\": \"C:\\\\Program Files\\\\Magnet Forensics\\\\Magnet AUTOMATE\\\\agent\\\\AXIOM Process\\\\AXIOMProcess.CLI.exe\"}]"

mf-automate-nodes-list


Retrieves a list of all available nodes (agents).

Base Command

mf-automate-nodes-list

Input

Argument Name Description Required
limit The maximum number of results to return. Default value is 50. Default is 50. Optional
all_results Whether to return all of the results. Overrides the limit argument if used. Default value is False. Optional

Context Output

Path Type Description
MagnetAutomate.Node.id Number The unique identifier of the node.
MagnetAutomate.Node.name String The name of the node.
MagnetAutomate.Node.status String The current status of the node.
MagnetAutomate.Node.workingDirectory String The working directory of the node.
MagnetAutomate.Node.address String The network address of the node.
MagnetAutomate.Node.applications Unknown A list of applications installed on the node.

Command Example

!mf-automate-nodes-list limit=10

mf-automate-node-update


Updates the configuration of an existing node.

Base Command

mf-automate-node-update

Input

Argument Name Description Required
node_id The unique identifier of the node to update. Required
address The new network address or hostname of the node. Optional
working_directory The new working directory for the node. Optional
applications_json A JSON array of applications installed on the node. Optional

Context Output

There is no context output for this command.

Command Example

!mf-automate-node-update node_id=1 address="automate-node-updated" working_directory="C:\\automate\\updatedTemp" applications_json="[{\"applicationName\": \"AXIOM Process\", \"applicationVersion\": \"7.1.0\"}]"

mf-automate-node-delete


Deletes a specific node from Magnet Automate.

Base Command

mf-automate-node-delete

Input

Argument Name Description Required
node_id The unique identifier of the node to delete. Required

Context Output

There is no context output for this command.

Command Example

!mf-automate-node-delete node_id=1

Configuration parameters

  • url — Server URL (required)
  • credentials — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (17)

  • mf-automate-case-cancel

    Cancels an ongoing case in Magnet Automate.

  • mf-automate-case-create

    Creates a new case in Magnet Automate. Use mf-automate-custom-fields-list to get all available custom fields.

  • mf-automate-case-delete

    Deletes a specific case from Magnet Automate.

  • mf-automate-cases-list

    Retrieves a list of all cases or information about a specific case.

  • mf-automate-custom-fields-list

    Retrieves a list of custom fields for cases and evidence sources.

  • mf-automate-merge-workflow-run-start

    Starts a merge workflow run for multiple existing workflow runs.

  • mf-automate-node-create

    Creates a new node (agent) in Magnet Automate.

  • mf-automate-node-delete

    Deletes a specific node from Magnet Automate.

  • mf-automate-node-update

    Updates the configuration of an existing node.

  • mf-automate-nodes-list

    Retrieves a list of all available nodes (agents).

  • mf-automate-workflow-delete

    Deletes a specific workflow from Magnet Automate.

  • mf-automate-workflow-get

    Retrieves detailed information and export data for a specific workflow. Use mf-automate-workflow-list to get all available workflows.

  • mf-automate-workflow-list

    Retrieves a list of all available workflows.

  • mf-automate-workflow-run-cancel

    Cancels a specific workflow run.

  • mf-automate-workflow-run-delete

    Deletes a specific workflow run from a case.

  • mf-automate-workflow-run-list

    Retrieves a list of all workflow runs for a specific case or details of a specific run.

  • mf-automate-workflow-run-start

    Starts a new workflow run and associates it with a specific case.

# ruff: noqa: F403, F405
import demistomock as demisto
from CommonServerPython import *
from CommonServerUserPython import *

from typing import Any
from pydantic import AnyUrl, Field, SecretStr, validator  # pylint: disable=no-name-in-module

from ContentClientApiModule import *
from BaseContentApiModule import *

# region Constants

BASE_CONTEXT_OUTPUT_PREFIX = "MagnetAutomate"

# endregion

# region Helpers


def truncate_results(results: list[Any], limit: int | None = None, all_results: bool = False) -> list[Any]:
    """
    Truncates a list of results based on a limit or an override flag.

    Args:
        results (list[Any]): The list of results to truncate.
        limit (int | None): The maximum number of results to return.
        all_results (bool): If True, returns the full list regardless of the limit.

    Returns:
        list[Any]: The truncated slice of results.
    """
    if all_results:
        return results

    if limit is not None:
        return results[:limit]

    return results


def validate_json(value):
    if isinstance(value, str) and value:
        try:
            return json.loads(value)
        except json.JSONDecodeError as e:
            # not logging json value as it might contain sensitive information
            demisto.debug(f"[VALIDATION FAILED] Could not parse json from provided value with exception {e.msg}.")
            return value
    return value


# endregion

# region Parameters


class Credentials(ContentBaseModel):
    """Credentials model for API authentication."""

    # username field omitted because `hiddenusername: true` in YML
    password: SecretStr


class MagnetAutomateParams(BaseParams):
    """Integration parameters for Magnet Automate."""

    url: AnyUrl
    credentials: Credentials

    @property
    def api_key(self):
        return self.credentials.password


# endregion

# region Auth & Client


class MagnetAutomateAuthHandler(APIKeyAuthHandler):
    """Custom authentication handler for Magnet Automate."""

    def __init__(self, api_key: SecretStr):
        super().__init__(key=api_key.get_secret_value(), header_name="X-API-KEY")


class MagnetAutomateClient(ContentClient):
    """Client for Magnet Automate API."""

    def __init__(self, params: MagnetAutomateParams):
        auth_handler = MagnetAutomateAuthHandler(params.api_key)
        super().__init__(
            base_url=params.url,
            verify=params.verify,
            proxy=params.proxy,
            auth_handler=auth_handler,
            client_name="MagnetAutomateClient",
        )

    def custom_fields_list(self) -> list[dict[str, Any]]:
        """
        Gets custom fields for cases and evidence sources.

        Returns:
            list[dict[str, Any]]: A list of custom fields.
        """

        demisto.debug("Sending a GET Request to /api/v0/customFields.")

        return self.get(
            url_suffix="/api/v0/customFields",
            resp_type="json",
        )

    def case_create(self, case_number: str, custom_field_values: dict[str, Any] | None = None) -> dict[str, Any]:
        """
        Creates a new case.

        Args:
            case_number (str): The case number.
            custom_field_values (dict[str, Any] | None): Optional custom field values.

        Returns:
            dict[str, Any]: The created case.
        """
        json_data: dict[str, Any] = assign_params(caseNumber=case_number, customFieldValues=custom_field_values)

        demisto.debug(f"Sending a POST Request to /api/v0/cases with {json_data=}.")

        return self.post(
            url_suffix="/api/v0/cases",
            json_data=json_data,
            resp_type="json",
        )

    def cases_list(self, case_id: int | None = None) -> list[dict[str, Any]] | dict[str, Any]:
        """
        Gets a list of all cases or information about a specific case.

        Args:
            case_id (int | None): The ID of the case to get.

        Returns:
            list[dict[str, Any]] | dict[str, Any]: A list of cases or a specific case.
        """
        url_suffix = "/api/v0/cases"
        if case_id:
            url_suffix += f"/{case_id}"

        demisto.debug(f"Sending a GET Request to {url_suffix}.")

        return self.get(
            url_suffix=url_suffix,
            resp_type="json",
        )

    def case_delete(self, case_id: int) -> None:
        """
        Deletes a case.

        Args:
            case_id (int): The ID of the case to delete.
        """
        demisto.debug(f"Sending a DELETE Request to /api/v0/cases/{case_id}.")

        self.delete(
            url_suffix=f"/api/v0/cases/{case_id}",
            resp_type="text",
        )

    def case_cancel(self, case_id: int) -> None:
        """
        Cancels a case.

        Args:
            case_id (int): The ID of the case to cancel.
        """
        demisto.debug(f"Sending a PUT Request to /api/v0/cases/{case_id}/cancel.")

        self.put(
            url_suffix=f"/api/v0/cases/{case_id}/cancel",
            resp_type="text",
        )

    def workflow_run_start(
        self,
        case_id: int,
        evidence_number: str,
        evidence_type: dict[str, Any],
        workflow_id: int,
        output_path: str | None = None,
        platform: str | None = None,
        decryption: dict[str, Any] | None = None,
        custom_field_values: dict[str, Any] | None = None,
        assigned_node_name: str | None = None,
    ) -> list[dict[str, Any]]:
        """
        Starts a workflow run and assigns it to a case.

        Args:
            case_id (int): The ID of the case to associate the workflow run with.
            evidence_number (str): An evidence number to apply to the evidence source.
            evidence_type (dict[str, Any]): The evidence type (e.g., {"ImageSource": {"path": "..."}}).
            workflow_id (int): The ID of the workflow to run.
            output_path (str | None): Optional output path.
            platform (str | None): Optional platform.
            decryption (dict[str, Any] | None): Optional decryption options.
            custom_field_values (dict[str, Any] | None): Optional custom field values.
            assigned_node_name (str | None): Optional assigned node name.

        Returns:
            list[dict[str, Any]]: The started workflow runs.
        """
        evidence_source: dict[str, Any] = assign_params(
            evidenceNumber=evidence_number,
            type=evidence_type,
            workflowId=workflow_id,
            outputPath=output_path,
            platform=platform,
            decryption=decryption,
            customFieldValues=custom_field_values,
            assignedNodeName=assigned_node_name,
        )

        json_data = {"evidenceSources": [evidence_source]}

        demisto.debug(f"Sending a POST Request to /api/v0/cases/{case_id}/runs with {json_data=}.")

        if decryption and "value" in decryption and hasattr(decryption["value"], "get_secret_value"):
            # remove obfuscation from payload before http post method
            raw_decryption_dict = decryption.copy()
            raw_decryption_dict["value"] = decryption["value"].get_secret_value()
            evidence_source["decryption"] = raw_decryption_dict

        return self.post(
            url_suffix=f"/api/v0/cases/{case_id}/runs",
            json_data=json_data,
            resp_type="json",
        )

    def workflow_run_delete(self, case_id: int, run_id: int) -> None:
        """
        Deletes a workflow run.

        Args:
            case_id (int): The ID of the case.
            run_id (int): The ID of the workflow run to delete.
        """
        demisto.debug(f"Sending a DELETE Request to /api/v0/cases/{case_id}/runs/{run_id}.")

        self.delete(
            url_suffix=f"/api/v0/cases/{case_id}/runs/{run_id}",
            resp_type="text",
        )

    def workflow_run_cancel(self, case_id: int, run_id: int) -> None:
        """
        Cancels a workflow run.

        Args:
            case_id (int): The ID of the case.
            run_id (int): The ID of the workflow run to cancel.
        """
        demisto.debug(f"Sending a PUT Request to /api/v0/cases/{case_id}/runs/{run_id}.")

        self.put(
            url_suffix=f"/api/v0/cases/{case_id}/runs/{run_id}",
            resp_type="text",
        )

    def workflow_run_list_specific(self, case_id: int, run_id: int) -> dict[str, Any]:
        """
        Gets a specific workflow run from a specific case.

        Args:
            case_id (int): The ID of the case to get workflow runs for.
            run_id (int): The ID of the specific workflow run.

        Returns:
            dict[str, Any]: A workflow run object.
        """
        demisto.debug(f"Sending a GET Request to /api/v0/cases/{case_id}/runs/{run_id}.")

        return self.get(
            url_suffix=f"/api/v0/cases/{case_id}/runs/{run_id}",
            resp_type="json",
        )

    def workflow_run_list_all(self, case_id: int) -> list[dict[str, Any]]:
        """
        Gets a list of all workflow runs for a specific case.

        Args:
            case_id (int): The ID of the case to get workflow runs for.

        Returns:
            list[dict[str, Any]]: A list of workflow runs.
        """
        demisto.debug(f"Sending a GET Request to /api/v0/cases/{case_id}/runs.")

        return self.get(
            url_suffix=f"/api/v0/cases/{case_id}/runs",
            resp_type="json",
        )

    def merge_workflow_run_start(
        self,
        case_id: int,
        run_ids: list[int],
        workflow_id: int,
        output_path: str | None = None,
        assigned_node_name: str | None = None,
    ) -> dict[str, Any]:
        """
        Starts a merge workflow run for multiple existing workflow runs.

        Args:
            case_id (int): The ID of the case.
            run_ids (list[int]): The IDs of the workflow runs to merge.
            workflow_id (int): The ID of the workflow to run.
            output_path (str | None): Optional output path.
            assigned_node_name (str | None): Optional assigned node name.

        Returns:
            dict[str, Any]: The started merge workflow run.
        """
        json_data: dict[str, Any] = {
            "runIds": run_ids,
            "mergeWorkflow": assign_params(workflowId=workflow_id, outputPath=output_path, assignedNodeName=assigned_node_name),
        }

        demisto.debug(f"Sending a POST Request to /api/v0/cases/{case_id}/merge with {json_data=}.")

        return self.post(
            url_suffix=f"/api/v0/cases/{case_id}/merge",
            json_data=json_data,
            resp_type="json",
        )

    def workflows_list(self) -> list[dict[str, Any]]:
        """
        Gets a list of all workflows.

        Returns:
            list[dict[str, Any]]: A list of workflows.
        """
        demisto.debug("Sending a GET Request to /api/v0/workflows.")

        return self.get(
            url_suffix="/api/v0/workflows",
            resp_type="json",
        )

    def workflow_delete(self, workflow_id: int) -> None:
        """
        Deletes a workflow.

        Args:
            workflow_id (int): The ID of the workflow to delete.
        """
        demisto.debug(f"Sending a DELETE Request to /api/v0/workflows/{workflow_id}.")

        self.delete(
            url_suffix=f"/api/v0/workflows/{workflow_id}",
            resp_type="text",
        )

    def workflow_get(self, workflow_id: int) -> dict[str, Any]:
        """
        Gets workflow export.

        Args:
            workflow_id (int): The ID of the workflow to export.

        Returns:
            dict[str, Any]: The exported workflow.
        """
        demisto.debug(f"Sending a GET Request to /api/v0/workflows/{workflow_id}/generate-export.")

        return self.get(
            url_suffix=f"/api/v0/workflows/{workflow_id}/generate-export",
            resp_type="json",
        )

    def node_create(
        self,
        name: str | None = None,
        address: str | None = None,
        working_directory: str | None = None,
        applications: list[dict[str, Any]] | None = None,
    ) -> dict[str, Any]:
        """
        Creates a new node.

        Args:
            name (str | None): The name of the node.
            address (str | None): The address of the node.
            working_directory (str | None): The working directory of the node.
            applications (list[dict[str, Any]] | None): Information about the applications installed on the node.

        Returns:
            dict[str, Any]: The created node.
        """
        json_data: dict[str, Any] = assign_params(
            name=name, address=address, workingDirectory=working_directory, applications=applications
        )

        demisto.debug(f"Sending a POST Request to /api/v0/nodes with {json_data=}.")

        return self.post(
            url_suffix="/api/v0/nodes",
            json_data=json_data,
            resp_type="json",
        )

    def nodes_list(self) -> list[dict[str, Any]]:
        """
        Gets a list of all the available nodes.

        Returns:
            list[dict[str, Any]]: A list of nodes.
        """
        demisto.debug("Sending a GET Request to /api/v0/nodes.")

        return self.get(
            url_suffix="/api/v0/nodes",
            resp_type="json",
        )

    def node_update(
        self,
        node_id: int,
        address: str | None = None,
        working_directory: str | None = None,
        applications: list[dict[str, Any]] | None = None,
    ) -> dict[str, Any]:
        """
        Updates an existing node.

        Args:
            node_id (int): The ID of the node to update.
            address (str | None): The address of the node.
            working_directory (str | None): The working directory of the node.
            applications (list[dict[str, Any]] | None): Information about the applications installed on the node.

        Returns:
            dict[str, Any]: The updated node.
        """
        json_data: dict[str, Any] = assign_params(address=address, workingDirectory=working_directory, applications=applications)

        demisto.debug(f"Sending a PUT Request to /api/v0/nodes/{node_id} with {json_data=}.")

        return self.put(
            url_suffix=f"/api/v0/nodes/{node_id}",
            json_data=json_data,
            resp_type="json",
        )

    def node_delete(self, node_id: int) -> None:
        """
        Deletes an existing node.

        Args:
            node_id (int): The ID of the node to delete.
        """
        demisto.debug(f"Sending a DELETE Request to /api/v0/nodes/{node_id}.")

        self.delete(
            url_suffix=f"/api/v0/nodes/{node_id}",
            resp_type="text",
        )


# endregion

# region test-module


def test_module(client: MagnetAutomateClient) -> str:
    """
    Verifies the connectivity with the Magnet Automate API.

    This function attempts to list custom fields with a limit of 1 to ensure
    that the provided API key and Server URL are valid and reachable.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.

    Returns:
        str: Returns "ok" if the connection is successful, otherwise an error message.
    """
    try:
        demisto.debug("[Testing] Testing API connectivity")
        args = CustomFieldsListArgs(limit=1, all_results=False)
        custom_fields_list_command(client, args)
        demisto.debug("[Testing] API connectivity test passed")

    except Exception as e:
        return f"AuthenticationError: Connection failed. Make sure Server URL and API Key are correctly set. {str(e)}"

    demisto.debug("[Testing] All tests passed.")
    return "ok"


# endregion


# region mf-automate-custom-fields-list
class CustomFieldsListArgs(ContentBaseModel):
    limit: int | None = Field(50, alias="limit")
    all_results: bool = Field(False, alias="all_results")

    @validator("limit", pre=True, allow_reuse=True)
    @classmethod
    def validate_limit(cls, v):
        return arg_to_number(v)

    @validator("all_results", pre=True, allow_reuse=True)
    @classmethod
    def validate_all_results(cls, v):
        return argToBoolean(v)


def custom_fields_list_command(client: MagnetAutomateClient, args: CustomFieldsListArgs) -> CommandResults:
    """
    Executes the mf-automate-custom-fields-list command.

    Retrieves a list of custom fields for cases and evidence sources from Magnet Automate,
    optionally filtered by a workflow ID.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (CustomFieldsListArgs): The command arguments including workflow_id, limit, and all_results.

    Returns:
        CommandResults: The results of the command execution, including the list of custom fields.
    """
    results = client.custom_fields_list()
    paginated_results = truncate_results(results, limit=args.limit, all_results=args.all_results)

    readable_output = tableToMarkdown(
        "Custom Fields",
        paginated_results,
        headers=[
            "id",
            "name",
            "type",
            "elementType",
            "description",
            "required",
            "exposeInWorkflow",
            "variableName",
        ],
        headerTransform=lambda x: {
            "id": "Field Id",
            "name": "Name",
            "type": "Type",
            "elementType": "Element Type",
            "description": "Description",
            "required": "Required",
            "exposeInWorkflow": "Expose In Workflow",
            "variableName": "Variable Name",
        }.get(x, x),
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.CustomFields",
        outputs_key_field="id",
        outputs=paginated_results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-case-create


class CaseCreateArgs(ContentBaseModel):
    case_number: str = Field(alias="case_number")
    custom_field_values: dict[str, Any] | None = Field(None, alias="custom_field_values")

    @validator("custom_field_values", pre=True, allow_reuse=True)
    @classmethod
    def validate_custom_field_values(cls, v):
        return validate_json(v)


def case_create_command(client: MagnetAutomateClient, args: CaseCreateArgs) -> CommandResults:
    """
    Executes the mf-automate-case-create command.

    Creates a new case in Magnet Automate with the specified case number and optional custom field values.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (CaseCreateArgs): The command arguments including case_number and custom_field_values.

    Returns:
        CommandResults: The results of the command execution, including the created case details.
    """
    results = client.case_create(case_number=args.case_number, custom_field_values=args.custom_field_values)

    readable_output = tableToMarkdown(
        "Case Created",
        results,
        headers=["id", "caseNumber", "customFieldValues"],
        headerTransform=lambda x: {
            "id": "Case Id",
            "caseNumber": "Case Number",
            "customFieldValues": "Custom Fields",
        }.get(x, x),
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.Case",
        outputs_key_field="id",
        outputs=results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-cases-list


class CasesListArgs(ContentBaseModel):
    case_id: int | None = Field(None, alias="case_id")
    limit: int | None = Field(50, alias="limit")
    all_results: bool = Field(False, alias="all_results")

    @validator("case_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_case_id(cls, v):
        return arg_to_number(v)

    @validator("limit", pre=True, allow_reuse=True)
    @classmethod
    def validate_limit(cls, v):
        return arg_to_number(v)

    @validator("all_results", pre=True, allow_reuse=True)
    @classmethod
    def validate_all_results(cls, v):
        return argToBoolean(v)


def cases_list_command(client: MagnetAutomateClient, args: CasesListArgs) -> CommandResults:
    """
    Executes the mf-automate-cases-list command.

    Retrieves a list of all cases or detailed information about a specific case from Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (CasesListArgs): The command arguments including case_id, limit, and all_results.

    Returns:
        CommandResults: The results of the command execution, including the case(s) information.
    """
    results = client.cases_list(case_id=args.case_id)

    if args.case_id:
        # Single case result
        case = results if isinstance(results, dict) else results[0] if results else {}

        readable_output = tableToMarkdown(
            f"Case {args.case_id} Details",
            case,
            headers=[
                "id",
                "caseNumber",
                "status",
                "startDateTime",
                "endDateTime",
                "duration",
                "caseRunDetails",
            ],
            headerTransform=lambda x: {
                "id": "Case Id",
                "caseNumber": "Case Number",
                "status": "Status",
                "startDateTime": "Start Date Time",
                "endDateTime": "End Date Time",
                "duration": "Duration",
                "caseRunDetails": "Case Run Details",
            }.get(x, x),
            removeNull=True,
        )
        return CommandResults(
            outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.Case",
            outputs_key_field="id",
            outputs=case,
            readable_output=readable_output,
            raw_response=results,
        )

    # List of cases
    paginated_results = truncate_results(results, limit=args.limit, all_results=args.all_results)  # type: ignore
    readable_output = tableToMarkdown(
        "Cases List",
        paginated_results,
        headers=["id", "caseNumber"],
        headerTransform=lambda x: {"id": "Case Id", "caseNumber": "Case Number"}.get(x, x),
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.Case",
        outputs_key_field="id",
        outputs=paginated_results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-case-delete


class CaseDeleteArgs(ContentBaseModel):
    case_id: int = Field(alias="case_id")

    @validator("case_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_case_id(cls, v):
        return arg_to_number(v, required=True)


def case_delete_command(client: MagnetAutomateClient, args: CaseDeleteArgs) -> CommandResults:
    """
    Executes the mf-automate-case-delete command.

    Deletes a specific case from Magnet Automate using its unique identifier.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (CaseDeleteArgs): The command arguments including the case_id to delete.

    Returns:
        CommandResults: A message indicating the successful deletion of the case.
    """
    client.case_delete(case_id=args.case_id)

    return CommandResults(readable_output=f"Case {args.case_id} deleted successfully")


# endregion

# region mf-automate-case-cancel


class CaseCancelArgs(ContentBaseModel):
    case_id: int = Field(alias="case_id")

    @validator("case_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_case_id(cls, v):
        return arg_to_number(v, required=True)


def case_cancel_command(client: MagnetAutomateClient, args: CaseCancelArgs) -> CommandResults:
    """
    Executes the mf-automate-case-cancel command.

    Cancels an ongoing case in Magnet Automate using its unique identifier.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (CaseCancelArgs): The command arguments including the case_id to cancel.

    Returns:
        CommandResults: A message indicating the successful cancellation of the case.
    """
    client.case_cancel(case_id=args.case_id)

    return CommandResults(readable_output=f"Case {args.case_id} cancelled successfully")


# endregion

# region mf-automate-workflow-run-start


class WorkflowRunStartArgs(ContentBaseModel):
    case_id: int = Field(alias="case_id")
    evidence_number: str = Field(alias="evidence_number")
    evidence_type: dict[str, Any] = Field(alias="type")
    workflow_id: int = Field(alias="workflow_id")
    output_path: str | None = Field(None, alias="output_path")
    platform: str | None = Field(None, alias="platform")
    decryption_type: str | None = Field(None, alias="decryption_type")
    decryption_value: SecretStr | None = Field(None, alias="decryption_value")
    continue_on_decryption_fail: bool | None = Field(None, alias="continue_on_decryption_fail")
    custom_field_values: dict[str, Any] | None = Field(None, alias="custom_field_values")
    assigned_node_name: str | None = Field(None, alias="assigned_node_name")

    @validator("case_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_case_id(cls, v):
        return arg_to_number(v, required=True)

    @validator("evidence_type", pre=True, allow_reuse=True)
    @classmethod
    def validate_evidence_type(cls, v):
        return validate_json(v)

    @validator("workflow_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_workflow_id(cls, v):
        return arg_to_number(v, required=True)

    @validator("continue_on_decryption_fail", pre=True, allow_reuse=True)
    @classmethod
    def validate_continue_on_decryption_fail(cls, v):
        return arg_to_bool_or_none(v)

    @validator("custom_field_values", pre=True, allow_reuse=True)
    @classmethod
    def validate_custom_field_values(cls, v):
        return validate_json(v)


def workflow_run_start_command(client: MagnetAutomateClient, args: WorkflowRunStartArgs) -> CommandResults:
    """
    Executes the mf-automate-workflow-run-start command.

    Starts a new workflow run in Magnet Automate and associates it with a specific case.
    Supports various options such as evidence type, decryption, and custom fields.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (WorkflowRunStartArgs): The command arguments including case_id, evidence details, and workflow_id.

    Returns:
        CommandResults: The results of the command execution, including the started workflow run details.
    """
    decryption = None
    if args.decryption_type:
        decryption = {
            "type": args.decryption_type,
            "value": args.decryption_value,
            "continueOnDecryptionFail": args.continue_on_decryption_fail or False,
        }

    results = client.workflow_run_start(
        case_id=args.case_id,
        evidence_number=args.evidence_number,
        evidence_type=args.evidence_type,
        workflow_id=args.workflow_id,
        output_path=args.output_path,
        platform=args.platform,
        decryption=decryption,
        custom_field_values=args.custom_field_values,
        assigned_node_name=args.assigned_node_name,
    )

    readable_output = tableToMarkdown(
        "Workflow Run Started",
        results,
        headers=[
            "id",
            "path",
            "version",
            "caseId",
            "caseTypeId",
            "basePath",
            "automateVersion",
        ],
        headerTransform=lambda x: {
            "id": "Run Id",
            "path": "Path",
            "version": "Version",
            "caseId": "Case Id",
            "caseTypeId": "Case Type Id",
            "basePath": "Base Path",
            "automateVersion": "Automate Version",
        }.get(x, x),
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.WorkflowRun",
        outputs_key_field="id",
        outputs=results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-workflow-run-list


class WorkflowRunListArgs(ContentBaseModel):
    case_id: int = Field(alias="case_id")
    run_id: int | None = Field(None, alias="run_id")
    limit: int | None = Field(50, alias="limit")
    all_results: bool = Field(False, alias="all_results")

    @validator("case_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_case_id(cls, v):
        return arg_to_number(v, required=True)

    @validator("run_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_run_id(cls, v):
        return arg_to_number(v)

    @validator("limit", pre=True, allow_reuse=True)
    @classmethod
    def validate_limit(cls, v):
        return arg_to_number(v)

    @validator("all_results", pre=True, allow_reuse=True)
    @classmethod
    def validate_all_results(cls, v):
        return argToBoolean(v)


def workflow_run_list_markdown(results: list[dict[str, Any]], title: str) -> str:
    """
    Converts workflow run results into a markdown table.

    Args:
        results (list[dict[str, Any]]): A list of workflow run objects to format.
        title (str): The title for the markdown table.

    Returns:
        str: A markdown table representing the workflow runs, including details about evidence,
            status, workflow, and current stage.
    """
    table_data = []
    for run in results:
        evidence = run.get("evidence", {}) or {}
        current_stage = run.get("currentStage", {}) or {}
        created_by = run.get("createdBy", {}) or {}

        table_data.append(
            {
                "Run Id": run.get("id"),
                "Evidence Id": evidence.get("id"),
                "Evidence Path": evidence.get("path"),
                "Evidence Type": evidence.get("evidenceType"),
                "Evidence Number": evidence.get("evidenceNumber"),
                "Selected Platform": evidence.get("selectedPlatform"),
                "Status": run.get("status"),
                "Workflow Id": run.get("workflowId"),
                "Workflow Name": run.get("workflowName"),
                "Start DateTime": run.get("startDateTime"),
                "Current Stage Application Display Name": current_stage.get("applicationDisplayName"),
                "Current Stage Status": current_stage.get("status"),
                "End DateTime": run.get("endDateTime"),
                "Created By": created_by.get("name"),
            }
        )

    headers = list(table_data[0].keys()) if table_data else None

    return tableToMarkdown(
        title,
        table_data,
        headers=headers,
        removeNull=True,
    )


def workflow_run_list_command(client: MagnetAutomateClient, args: WorkflowRunListArgs) -> CommandResults:
    """
    Executes the mf-automate-workflow-run-list command.

    Retrieves a list or a specific workflow run associated with a specific case.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (WorkflowRunListArgs): The command arguments including case_id and optional run_id, limit, and all_results.

    Returns:
        CommandResults: The results of the command execution, including the list of workflow runs.
    """
    if args.run_id:
        # single run
        result = client.workflow_run_list_specific(case_id=args.case_id, run_id=args.run_id)
        readable_output = workflow_run_list_markdown(
            [result],
            f"Workflow Run {args.run_id} Details",
        )

        return CommandResults(
            outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.WorkflowRun",
            outputs_key_field="id",
            outputs=result,
            readable_output=readable_output,
            raw_response=result,
        )

    # all runs
    results = client.workflow_run_list_all(case_id=args.case_id)
    paginated_results = truncate_results(results, limit=args.limit, all_results=args.all_results)
    readable_output = workflow_run_list_markdown(
        paginated_results,
        f"Workflow Runs for Case {args.case_id}",
    )

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.WorkflowRun",
        outputs_key_field="id",
        outputs=paginated_results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-workflow-run-delete


class WorkflowRunDeleteArgs(ContentBaseModel):
    case_id: int = Field(alias="case_id")
    run_id: int = Field(alias="run_id")

    @validator("case_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_case_id(cls, v):
        return arg_to_number(v, required=True)

    @validator("run_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_run_id(cls, v):
        return arg_to_number(v, required=True)


def workflow_run_delete_command(client: MagnetAutomateClient, args: WorkflowRunDeleteArgs) -> CommandResults:
    """
    Executes the mf-automate-workflow-run-delete command.

    Deletes a specific workflow run from a case in Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (WorkflowRunDeleteArgs): The command arguments including case_id and run_id.

    Returns:
        CommandResults: The results of the command execution.
    """
    client.workflow_run_delete(case_id=args.case_id, run_id=args.run_id)

    return CommandResults(readable_output=f"Workflow run {args.run_id} for case {args.case_id} deleted successfully")


# endregion

# region mf-automate-workflow-run-cancel


class WorkflowRunCancelArgs(ContentBaseModel):
    case_id: int = Field(alias="case_id")
    run_id: int = Field(alias="run_id")

    @validator("case_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_case_id(cls, v):
        return arg_to_number(v, required=True)

    @validator("run_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_run_id(cls, v):
        return arg_to_number(v, required=True)


def workflow_run_cancel_command(client: MagnetAutomateClient, args: WorkflowRunCancelArgs) -> CommandResults:
    """
    Executes the mf-automate-workflow-run-cancel command.

    Cancels a specific workflow run in Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (WorkflowRunCancelArgs): The command arguments including case_id and run_id.

    Returns:
        CommandResults: The results of the command execution.
    """
    client.workflow_run_cancel(case_id=args.case_id, run_id=args.run_id)

    return CommandResults(readable_output=f"Workflow run {args.run_id} for case {args.case_id} cancelled successfully")


# endregion

# region mf-automate-merge-workflow-run-start


class MergeWorkflowRunStartArgs(ContentBaseModel):
    case_id: int = Field(alias="case_id")
    run_ids: list[int] = Field(alias="run_ids")
    workflow_id: int = Field(alias="workflow_id")
    output_path: str | None = Field(None, alias="output_path")
    assigned_node_name: str | None = Field(None, alias="assigned_node_name")

    @validator("case_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_case_id(cls, v):
        return arg_to_number(v, required=True)

    @validator("run_ids", pre=True, allow_reuse=True)
    @classmethod
    def validate_run_ids(cls, v):
        return argToList(v, transform=lambda x: arg_to_number(x, required=True))

    @validator("workflow_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_workflow_id(cls, v):
        return arg_to_number(v, required=True)


def merge_workflow_run_start_command(client: MagnetAutomateClient, args: MergeWorkflowRunStartArgs) -> CommandResults:
    """
    Executes the mf-automate-merge-workflow-run-start command.

    Starts a merge workflow run for multiple existing workflow runs in Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (MergeWorkflowRunStartArgs): The command arguments including case_id, run_ids, and workflow_id.

    Returns:
        CommandResults: The results of the command execution, including the started merge workflow run details.
    """
    results = client.merge_workflow_run_start(
        case_id=args.case_id,
        run_ids=args.run_ids,
        workflow_id=args.workflow_id,
        output_path=args.output_path,
        assigned_node_name=args.assigned_node_name,
    )

    readable_output = tableToMarkdown(
        "Merge Workflow Run Started",
        results,
        headers=[
            "id",
            "path",
            "version",
            "caseId",
            "caseTypeId",
            "basePath",
            "automateVersion",
        ],
        headerTransform=lambda x: {
            "id": "Run Id",
            "path": "Path",
            "version": "Version",
            "caseId": "Case Id",
            "caseTypeId": "Case Type Id",
            "basePath": "Base Path",
            "automateVersion": "Automate Version",
        }.get(x, x),
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.WorkflowRun",
        outputs_key_field="id",
        outputs=results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-workflow-list


class WorkflowListArgs(ContentBaseModel):
    limit: int | None = Field(50, alias="limit")
    all_results: bool = Field(False, alias="all_results")

    @validator("limit", pre=True, allow_reuse=True)
    @classmethod
    def validate_limit(cls, v):
        return arg_to_number(v)

    @validator("all_results", pre=True, allow_reuse=True)
    @classmethod
    def validate_all_results(cls, v):
        return argToBoolean(v)


def workflow_list_markdown(results: list[dict[str, Any]]) -> str:
    """
    Converts workflow list results into a markdown table.

    Args:
        results (list[dict[str, Any]]): A list of workflow objects to format.

    Returns:
        str: A markdown table representing the workflows, including ID, Name, Type,
            Description, and Output Path.
    """
    table_data = []
    for workflow in results:
        workflow_type = workflow.get("type", {}) or {}

        table_data.append(
            {
                "Id": workflow.get("id"),
                "Name": workflow.get("name"),
                "Type Name": workflow_type.get("name"),
                "Description": workflow.get("description"),
                "Output Path": workflow.get("outputPath"),
            }
        )

    return tableToMarkdown(
        "Workflows",
        table_data,
        headers=["Id", "Name", "Type Name", "Description", "Output Path"],
        removeNull=True,
    )


def workflow_list_command(client: MagnetAutomateClient, args: WorkflowListArgs) -> CommandResults:
    """
    Executes the mf-automate-workflow-list command.

    Retrieves a list of available workflows from Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (WorkflowListArgs): The command arguments including limit and all_results.

    Returns:
        CommandResults: The results of the command execution, including the list of workflows.
    """
    results = client.workflows_list()
    paginated_results = truncate_results(results, limit=args.limit, all_results=args.all_results)

    readable_output = workflow_list_markdown(paginated_results)

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.Workflow",
        outputs_key_field="id",
        outputs=paginated_results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-workflow-delete


class WorkflowDeleteArgs(ContentBaseModel):
    workflow_id: int = Field(alias="workflow_id")

    @validator("workflow_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_workflow_id(cls, v):
        return arg_to_number(v, required=True)


def workflow_delete_command(client: MagnetAutomateClient, args: WorkflowDeleteArgs) -> CommandResults:
    """
    Executes the mf-automate-workflow-delete command.

    Deletes a specific workflow from Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (WorkflowDeleteArgs): The command arguments including the workflow_id to delete.

    Returns:
        CommandResults: The results of the command execution.
    """
    client.workflow_delete(workflow_id=args.workflow_id)

    return CommandResults(readable_output=f"Workflow {args.workflow_id} deleted successfully")


# endregion

# region mf-automate-workflow-get


class WorkflowGetArgs(ContentBaseModel):
    workflow_id: int = Field(alias="workflow_id")

    @validator("workflow_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_workflow_id(cls, v):
        return arg_to_number(v, required=True)


def workflow_get_markdown(results: dict[str, Any], workflow_id: int) -> str:
    """
    Converts a workflow export object into a markdown table.

    Args:
        results (dict[str, Any]): The workflow export data.
        workflow_id (int): The ID of the workflow being exported.

    Returns:
        str: A markdown table representing the workflow export details, including version,
            name, description, source configuration, and paths.
    """
    source_config = results.get("sourceConfig", {}) or {}

    table_data = {
        "Automate Version": results.get("automateVersion"),
        "Name": results.get("name"),
        "Description": results.get("description"),
        "Source Type": results.get("sourceType"),
        "Source Config Image": source_config.get("image"),
        "Output Path": results.get("outputPath"),
        "Key List Path": results.get("keylistPath"),
        "Password List Path": results.get("passwordListPath"),
        "Distribution": results.get("distribution"),
        "Local Mode": results.get("localMode"),
    }

    return tableToMarkdown(
        f"Workflow {workflow_id} Export",
        table_data,
        headers=[
            "Automate Version",
            "Name",
            "Description",
            "Source Type",
            "Source Config Image",
            "Output Path",
            "Key List Path",
            "Password List Path",
            "Distribution",
            "Local Mode",
        ],
        removeNull=True,
    )


def workflow_get_command(client: MagnetAutomateClient, args: WorkflowGetArgs) -> CommandResults:
    """
    Executes the mf-automate-workflow-get command.

    Retrieves detailed information about a specific workflow from Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (WorkflowGetArgs): The command arguments including the workflow_id.

    Returns:
        CommandResults: The results of the command execution.
    """
    results = client.workflow_get(workflow_id=args.workflow_id)

    readable_output = workflow_get_markdown(results, args.workflow_id)

    # extend the response with the workflow id
    context_output = results.copy()
    context_output["id"] = args.workflow_id

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.Workflow",
        outputs_key_field="id",
        outputs=context_output,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-node-create


class NodeCreateArgs(ContentBaseModel):
    name: str | None = Field(None, alias="name")
    address: str | None = Field(None, alias="address")
    working_directory: str | None = Field(None, alias="working_directory")
    applications_json: Any | None = Field(None, alias="applications_json")

    @validator("applications_json", pre=True, allow_reuse=True)
    @classmethod
    def validate_applications_json(cls, v):
        return validate_json(v)


def node_create_command(client: MagnetAutomateClient, args: NodeCreateArgs) -> CommandResults:
    """
    Executes the mf-automate-node-create command.

    Creates a new node (agent) in Magnet Automate with the specified configuration.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (NodeCreateArgs): The command arguments including name, address, and working directory.

    Returns:
        CommandResults: The results of the command execution.
    """
    results = client.node_create(
        name=args.name,
        address=args.address,
        working_directory=args.working_directory,
        applications=args.applications_json,  # type: ignore
    )

    node_name = results.get("name", "Unknown")
    readable_output = f"The node '{node_name}' was created successfully"

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.Node",
        outputs_key_field="id",
        outputs=results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-nodes-list


class NodesListArgs(ContentBaseModel):
    limit: int | None = Field(50, alias="limit")
    all_results: bool = Field(False, alias="all_results")

    @validator("limit", pre=True, allow_reuse=True)
    @classmethod
    def validate_limit(cls, v):
        return arg_to_number(v)

    @validator("all_results", pre=True, allow_reuse=True)
    @classmethod
    def validate_all_results(cls, v):
        return argToBoolean(v)


def nodes_list_command(client: MagnetAutomateClient, args: NodesListArgs) -> CommandResults:
    """
    Executes the mf-automate-nodes-list command.

    Retrieves a list of all nodes (agents) configured in Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (NodesListArgs): The command arguments including limit and all_results.

    Returns:
        CommandResults: The results of the command execution.
    """
    results = client.nodes_list()
    paginated_results = truncate_results(results, limit=args.limit, all_results=args.all_results)

    readable_output = tableToMarkdown(
        "Nodes List",
        paginated_results,
        headers=["id", "name", "status", "workingDirectory", "address"],
        headerTransform=lambda x: {
            "id": "Node Id",
            "name": "Name",
            "status": "Status",
            "workingDirectory": "Working Directory",
            "address": "Address",
        }.get(x, x),
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix=f"{BASE_CONTEXT_OUTPUT_PREFIX}.Node",
        outputs_key_field="id",
        outputs=paginated_results,
        readable_output=readable_output,
        raw_response=results,
    )


# endregion

# region mf-automate-node-update


class NodeUpdateArgs(ContentBaseModel):
    node_id: int = Field(alias="node_id")
    address: str | None = Field(None, alias="address")
    working_directory: str | None = Field(None, alias="working_directory")
    applications_json: Any | None = Field(None, alias="applications_json")

    @validator("node_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_node_id(cls, v):
        return arg_to_number(v, required=True)

    @validator("applications_json", pre=True, allow_reuse=True)
    @classmethod
    def validate_applications_json(cls, v):
        return validate_json(v)


def node_update_command(client: MagnetAutomateClient, args: NodeUpdateArgs) -> CommandResults:
    """
    Executes the mf-automate-node-update command.

    Updates the configuration of an existing node in Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (NodeUpdateArgs): The command arguments including node_id and fields to update.

    Returns:
        CommandResults: The results of the command execution.
    """
    client.node_update(
        node_id=args.node_id,
        address=args.address,
        working_directory=args.working_directory,
        applications=args.applications_json,  # type: ignore
    )

    return CommandResults(readable_output=f"Node {args.node_id} was updated successfully")


# endregion

# region mf-automate-node-delete


class NodeDeleteArgs(ContentBaseModel):
    node_id: int = Field(alias="node_id")

    @validator("node_id", pre=True, allow_reuse=True)
    @classmethod
    def validate_node_id(cls, v):
        return arg_to_number(v, required=True)


def node_delete_command(client: MagnetAutomateClient, args: NodeDeleteArgs) -> CommandResults:
    """
    Executes the mf-automate-node-delete command.

    Deletes a specific node from Magnet Automate.

    Args:
        client (MagnetAutomateClient): The Magnet Automate API client.
        args (NodeDeleteArgs): The command arguments including the node_id to delete.

    Returns:
        CommandResults: The results of the command execution.
    """
    client.node_delete(node_id=args.node_id)

    return CommandResults(readable_output=f"The Node {args.node_id} was deleted successfully")


# endregion

# region ExecutionConfig


class MagnetAutomateExecutionConfig(BaseExecutionConfig):
    """Execution configuration for Magnet Automate."""

    @property
    def params(self) -> MagnetAutomateParams:
        return MagnetAutomateParams(**self._raw_params)

    @property
    def custom_fields_list_args(self) -> CustomFieldsListArgs:
        return CustomFieldsListArgs(**self._raw_args)

    @property
    def case_create_args(self) -> CaseCreateArgs:
        return CaseCreateArgs(**self._raw_args)

    @property
    def cases_list_args(self) -> CasesListArgs:
        return CasesListArgs(**self._raw_args)

    @property
    def case_delete_args(self) -> CaseDeleteArgs:
        return CaseDeleteArgs(**self._raw_args)

    @property
    def case_cancel_args(self) -> CaseCancelArgs:
        return CaseCancelArgs(**self._raw_args)

    @property
    def workflow_run_start_args(self) -> WorkflowRunStartArgs:
        return WorkflowRunStartArgs(**self._raw_args)

    @property
    def workflow_run_delete_args(self) -> WorkflowRunDeleteArgs:
        return WorkflowRunDeleteArgs(**self._raw_args)

    @property
    def workflow_run_cancel_args(self) -> WorkflowRunCancelArgs:
        return WorkflowRunCancelArgs(**self._raw_args)

    @property
    def workflow_run_list_args(self) -> WorkflowRunListArgs:
        return WorkflowRunListArgs(**self._raw_args)

    @property
    def merge_workflow_run_start_args(self) -> MergeWorkflowRunStartArgs:
        return MergeWorkflowRunStartArgs(**self._raw_args)

    @property
    def workflow_list_args(self) -> WorkflowListArgs:
        return WorkflowListArgs(**self._raw_args)

    @property
    def workflow_delete_args(self) -> WorkflowDeleteArgs:
        return WorkflowDeleteArgs(**self._raw_args)

    @property
    def workflow_get_args(self) -> WorkflowGetArgs:
        return WorkflowGetArgs(**self._raw_args)

    @property
    def node_create_args(self) -> NodeCreateArgs:
        return NodeCreateArgs(**self._raw_args)

    @property
    def nodes_list_args(self) -> NodesListArgs:
        return NodesListArgs(**self._raw_args)

    @property
    def node_update_args(self) -> NodeUpdateArgs:
        return NodeUpdateArgs(**self._raw_args)

    @property
    def node_delete_args(self) -> NodeDeleteArgs:
        return NodeDeleteArgs(**self._raw_args)


# endregion

# region Main


def main() -> None:
    """
    Main entry point for the Magnet Automate integration.

    Initializes the execution configuration, client, and dispatches the command
    to the appropriate command function.
    """
    execution = MagnetAutomateExecutionConfig()
    command = execution.command
    demisto.debug(f"[Main] Starting to execute {command=}.")

    try:
        params = execution.params
        client = MagnetAutomateClient(params)

        match command:
            case "test-module":
                return_results(test_module(client))

            case "mf-automate-custom-fields-list":
                return_results(custom_fields_list_command(client, execution.custom_fields_list_args))

            case "mf-automate-case-create":
                return_results(case_create_command(client, execution.case_create_args))

            case "mf-automate-cases-list":
                return_results(cases_list_command(client, execution.cases_list_args))

            case "mf-automate-case-delete":
                return_results(case_delete_command(client, execution.case_delete_args))

            case "mf-automate-case-cancel":
                return_results(case_cancel_command(client, execution.case_cancel_args))

            case "mf-automate-workflow-run-start":
                return_results(workflow_run_start_command(client, execution.workflow_run_start_args))

            case "mf-automate-workflow-run-list":
                return_results(workflow_run_list_command(client, execution.workflow_run_list_args))

            case "mf-automate-workflow-run-delete":
                return_results(workflow_run_delete_command(client, execution.workflow_run_delete_args))

            case "mf-automate-workflow-run-cancel":
                return_results(workflow_run_cancel_command(client, execution.workflow_run_cancel_args))

            case "mf-automate-merge-workflow-run-start":
                return_results(merge_workflow_run_start_command(client, execution.merge_workflow_run_start_args))

            case "mf-automate-workflow-list":
                return_results(workflow_list_command(client, execution.workflow_list_args))

            case "mf-automate-workflow-delete":
                return_results(workflow_delete_command(client, execution.workflow_delete_args))

            case "mf-automate-workflow-get":
                return_results(workflow_get_command(client, execution.workflow_get_args))

            case "mf-automate-node-create":
                return_results(node_create_command(client, execution.node_create_args))

            case "mf-automate-nodes-list":
                return_results(nodes_list_command(client, execution.nodes_list_args))

            case "mf-automate-node-update":
                return_results(node_update_command(client, execution.node_update_args))

            case "mf-automate-node-delete":
                return_results(node_delete_command(client, execution.node_delete_args))

            case _:
                raise NotImplementedError(f"Command {command} is not implemented.")

    except Exception as e:
        return_error(f"Failed to execute {command} command.\nError:\n{str(e)}")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()

# endregion