예제 #1
0
def create_message_list(selected_plugin: List[str], all_plugins: List[AgentDescriptor], verbose_mode=False) -> Action:
    text = 'Available Skills:\n'

    for plugin in all_plugins:
        if plugin.pkg_name in selected_plugin:
            text += Colorize() \
                .emoji(Colorize.EMOJI_CHECK) \
                .complete() \
                .append(f" {get_printable_name(plugin)}\n") \
                .to_console()

            if verbose_mode:
                text += Colorize() \
                    .complete() \
                    .append(f"{plugin.description}\n") \
                    .to_console()

        else:
            text += \
                Colorize() \
                    .emoji(Colorize.EMOJI_BOX) \
                    .append(f' {get_printable_name(plugin)}\n') \
                    .to_console()

            if verbose_mode:
                text += Colorize() \
                    .append(f"{plugin.description}\n") \
                    .to_console()

    return Action(
        suggested_command=':',
        description=text,
        execute=True
    )
예제 #2
0
def ask_user_prompt(command_to_execute: Action) -> Optional[str]:
    if command_to_execute.is_same_command():
        return command_to_execute.origin_command

    if command_to_execute.execute:
        return command_to_execute.suggested_command

    print(Colorize().emoji(
        Colorize.EMOJI_ROBOT).info().append("  Suggests: ").info().append(
            f"{command_to_execute.suggested_command} ").append(
                "(y/n/e)").to_console())

    while True:
        command_input = input()
        if is_yes_command(command_input):
            return command_to_execute.suggested_command

        if is_not_command(command_input):
            return command_to_execute.origin_command

        if is_explain_command(command_input):
            print(Colorize().warning().append(
                f"Description: {command_to_execute.description}").to_console())

        print(Colorize().info().append(
            'choose yes[y] or no[n] or explain[e]').to_console())
예제 #3
0
def create_message_server_runing() -> str:
    colorize = Colorize()
    if check_if_process_running():
        colorize \
            .complete() \
            .append(f"The server is running")
    else:
        colorize \
            .warning() \
            .append(f"The server is not running")

    return colorize.to_console()
예제 #4
0
파일: install.py 프로젝트: tmaone/clai
def ask_to_user(text):
    print(Colorize().info().append(f"{text} ").append("(y/n)").to_console())

    while True:
        command_input = input()
        if command_input in ('y', 'yes'):
            return True

        if command_input in ('n', 'no'):
            return False

        print(Colorize().info().append('choose yes[y] or no[n]').to_console())
예제 #5
0
def expected_description(all_plugins, selected) -> str:
    text = 'Available Skills:\n'

    for plugin in all_plugins:
        if plugin.pkg_name in selected:
            text += Colorize().emoji(Colorize.EMOJI_CHECK).complete() \
                .append(f" {get_printable_name(plugin)}\n") \
                .to_console()

        else:
            text += Colorize().emoji(Colorize.EMOJI_BOX) \
                .append(f' {get_printable_name(plugin)}\n') \
                .to_console()

    return text
예제 #6
0
    def get_next_action(self, state: State) -> Action:

        # to be ultimately replaced by a suitable intent recognizer
        # refer to the nlc2cmd skill for an example of a NLC layer
        if state.command in self.intents:

            # deploy to kube deploys local Dockerfile to your ibmcloud 
            # run Dockerfile brings up an application locally
            self.exe.set_goal(state.command)

            # this is the sequence of actions that achieves the user's goal    
            plan = self.exe.get_plan()

            if plan:

                logger.info("####### log plan inside ibmcloud ########")
                logger.info(plan)

                action_list = []
                for action in plan:
                    # translate from actions in the planner's domain to Action Class
                    action_object = self.exe.execute_action(action)
                    # some actions may have null executions (internal to the reasoning engine)
                    if action_object: action_list.append(action_object)

                return action_list

            else: return Action(suggested_command=NOOP_COMMAND, 
                                execute=True,
                                description=Colorize().info().append('Sorry could not find a plan to help! :-(').to_console(),
                                confidence=1.0)

        # does not do anything else by default
        else: return Action(suggested_command=NOOP_COMMAND)
예제 #7
0
파일: tellina.py 프로젝트: xzlin/clai
    def get_next_action(self, state: State) -> Action:

        # user typed in, in natural language
        command = state.command

        try:

            ## Needs to be a post request since service/endpoint is configured for post
            endpoint_comeback = requests.post(tellina_endpoint,
                                              json={
                                                  'command': command
                                              }).json()
            ## tellina endpoint must return a json with

            # tellina response, the cmd for the user NL utterance
            response = 'Try >> ' + endpoint_comeback['response']
            # the confidence; the tellina endpoint currently returns 0.0
            confidence = float(endpoint_comeback['confidence'])

            return Action(
                suggested_command=NOOP_COMMAND,
                execute=True,
                description=Colorize().info().append(response).to_console(),
                confidence=confidence)

        except Exception as ex:
            return [{"text": "Method failed with status " + str(ex)}, 0.0]
예제 #8
0
파일: nlc2cmd.py 프로젝트: xzlin/clai
    def get_next_action(self, state: State) -> Action:
        command = state.command
        data, confidence = self.service(command)
        response = data["text"]

        return Action(
            suggested_command=NOOP_COMMAND,
            execute=True,
            description=Colorize().info().append(response).to_console(),
            confidence=confidence)
예제 #9
0
    def post_execute(self, state: State) -> Action:
        if state.command.startswith('ls') and state.result_code != '0':
            return Action(description=Colorize()
                          .append(f"Are you sure that this command is correct?({state.result_code})\n")
                          .warning()
                          .append(f"Try man ls for more info ")
                          .to_console(),
                          confidence=1
                          )

        return Action(suggested_command=state.command)
예제 #10
0
def create_error_install(name: str) -> Action:
    text = Colorize() \
        .warning() \
        .append(f"{name} is not a valid skill to add to the catalog. You need to write a folder or a valid url. \n") \
        .append("Example: clai install ./my_new_agent") \
        .to_console()

    return Action(
        suggested_command=':',
        description=text,
        execute=True
    )
예제 #11
0
    def execute(self, state: State) -> Action:
        self.agent_datasource.reload()

        text = Colorize() \
            .complete() \
            .append("Plugins reloaded.\n") \
            .to_console()

        return Action(suggested_command=":",
                      execute=True,
                      description=text,
                      origin_command=state.command)
예제 #12
0
def create_error_select(selected_plugin: str) -> Action:
    text = Colorize() \
        .warning() \
        .append(f"{selected_plugin} is not a valid skill name. Write >> clai skills to check available skills.") \
        .append("Example: >> clai activate nlc2cmd") \
        .to_console()

    return Action(
        suggested_command=':',
        description=text,
        execute=True
    )
예제 #13
0
def create_orchestrator_list(selected_orchestrator: str,
                             all_orchestrator: List[OrchestratorDescriptor],
                             verbose_mode=False) -> Action:
    text = 'Available Orchestrators:\n'

    for orchestrator in all_orchestrator:
        if selected_orchestrator == orchestrator.name:
            text += Colorize() \
                .emoji(Colorize.EMOJI_CHECK) \
                .complete() \
                .append(f" {orchestrator.name}\n") \
                .to_console()

            if verbose_mode:
                text += Colorize() \
                    .complete() \
                    .append(f" {orchestrator.description}\n") \
                    .to_console()

        else:
            text += \
                Colorize() \
                    .emoji(Colorize.EMOJI_BOX) \
                    .append(f' {orchestrator.name}\n') \
                    .to_console()

            if verbose_mode:
                text += Colorize() \
                    .append(f" {orchestrator.description}\n") \
                    .to_console()

    return Action(
        suggested_command=':',
        description=text,
        execute=True
    )
예제 #14
0
def process_command_from_user(command_id, user_name, command_to_check):
    command_to_execute = send_command(command_id=command_id,
                                      user_name=user_name,
                                      command_to_check=command_to_check)

    if command_to_execute.origin_command == STOP_COMMAND:
        print(Colorize().info().append("Clai has been stopped").to_console())
        return NOOP_COMMAND, False

    if command_to_execute.description and command_to_execute.suggested_command == ":":
        print(command_to_execute.description)

    command_accepted_by_the_user = ask_user_prompt(command_to_execute)

    return command_accepted_by_the_user, command_to_execute.pending_actions
예제 #15
0
def process_command(command_id, user_name, command_to_check):
    pending_commands = False

    if command_to_check == COMMAND_START_SERVER:
        command_accepted_by_the_user = START_SERVER_COMMAND_TO_EXECUTE
        print(Colorize().info().append(
            "CLAI Starting. CLAI could take a while to start replying").
              to_console())
    elif command_to_check.strip() == COMMAND_BASE.strip():
        command_accepted_by_the_user = NOOP_COMMAND
        print(create_message_server_runing())
        print(create_message_help().description)
    else:
        command_accepted_by_the_user, pending_commands = \
            process_command_from_user(command_id, user_name, command_to_check)

    override_last_command("\n" + command_accepted_by_the_user + "\n")

    if not pending_commands:
        sys.exit(1)
예제 #16
0
파일: fix_bot.py 프로젝트: xzlin/clai
    def post_execute(self, state: State) -> Action:

        if state.result_code == '0':
            return Action(suggested_command=state.command)

        cmd = str(state.command)
        stderr = str(state.stderr)

        try:
            # Get corrected command from `thefuck` bot
            settings.init()
            cmd = Command(cmd, stderr)
            cmd_corrected = get_corrected_commands(cmd)

            cmd_to_run = next(cmd_corrected).script
        except Exception:
            return Action(suggested_command=state.command, confidence=0.1)
        else:
            return Action(description=Colorize().info().append(
                "Maybe you want to try: {}".format(cmd_to_run)).to_console(),
                          confidence=0.8)
예제 #17
0
def create_message_help() -> Action:
    text = Colorize().info() \
        .append("CLAI usage:\n"
                "clai [help] [skills [-v]] [activate [skill_name]] [deactivate [skill_name]] "
                "[manual | automatic] [install [name | url]] \n\n"
                "help           Print help and usage of clai.\n"
                "skills         List available skills. Use -v For a verbose description of each skill.\n"
                "activate       Activate the named skill.\n"
                "deactivate     Deactivate the named skill.\n"
                "manual         Disables automatic execution of commands without operator confirmation.\n"
                "auto           Enables automatic execution of commands without operator confirmation.\n"
                "install        Installs a new skill. The required argument may be a local file path\n"
                "               to a skill plugin folder, or it may be a URL to install a skill plugin \n"
                "               over a network connection.\n"
                ) \
        .to_console()

    return Action(
        suggested_command=':',
        description=text,
        execute=True
    )
예제 #18
0
def print_error(text):
    print(Colorize().warning().append(text).to_console())
예제 #19
0
파일: tldr_wrapper.py 프로젝트: xzlin/clai
def get_command_tldr(cmd):

    if not TLDR_AVAILABLE:
        return ''

    cmd_tldr = tldr.get_page(cmd)

    if cmd_tldr is None:
        return ''

    description = Colorize()

    for i, line in enumerate(cmd_tldr):
        line = line.rstrip().decode('utf-8')

        if i == 0:
            description.append('-' * 50 + '\n')

        if len(line) < 1:  # Empty line
            description.append('\n')
        elif line[0] == '#':
            line = line[1:]
            description.warning().append(line.strip() + '\n')
        elif line[0] == '>':  # Description line
            line = ' ' + line[1:]
            description.normal().append(line.strip() + '\n')
        elif line[0] == '-':  # Example line
            description.normal().append(line.strip() + '\n')
        elif line[0] == '`':  # Example command
            line = ' ' + line[1:-1]
            description.info().append(line.strip() + '\n')

    description.normal().append('summary provided by tldr package\n')
    description.normal().append('-' * 50 + '\n')

    return description.to_console()
예제 #20
0
파일: service.py 프로젝트: xzlin/clai
    def __call__(self, *args, **kwargs):

        # Set user command
        command = args[0]
        confidence = 0.0

        try:

            response = requests.post(_rasa_service, json={'text': command}).json()
            confidence = response["intent"]["confidence"]

            if confidence > self.state["threshold"]:
                self.state["current_intent"] = response["intent"]["name"]

        except Exception as ex:
            print("Method failed with status " + str(ex))

        if self.state["current_intent"] == "commit":

            if not self.state["ready_flag"]:
                self.state["ready_flag"] = True
                temp_description = "Ready to {}. Press execute to continue.".format(self.state["current_intent"])

                return [ Action(
                            suggested_command="git status | tee {}".format(_path_to_log_file),
                            execute=True,
                            description=None,
                            confidence=1.0
                            ), Action(
                                suggested_command=NOOP_COMMAND,
                                execute=True,
                                description=Colorize().info().append(temp_description).to_console(),
                                confidence=1.0
                                ) ] 

        if command == "execute":

            now = datetime.now()
            commit_message = now.strftime("%d/%m/%Y %H:%M:%S")

            # this should really go into post-execute #
            stdout = open(_path_to_log_file).read().split("\n")
            commit_description = ""
            current_branch = None

            for line in stdout:

                if line.startswith("On branch"):
                    current_branch = line.split()[-1]

                if line.strip().startswith("modified:"):
                    commit_description += line.strip() + " + "

            if commit_description: self.state["commit_details"] = commit_description
            if current_branch: self.state["current_branch"] = current_branch

            commit_description = self.state["commit_details"]

            respond = Action(
                        suggested_command='git commit -m "{}" -m "{}";'.format(commit_message, commit_description),
                        execute=False,
                        description=None,
                        confidence=1.0
                    )

            if self.state["has_done_add"]:
                return respond

            else: 
                return [ Action(
                            suggested_command='git add -A',
                            execute=True,
                            description="Adding untracked files...",
                            confidence=1.0
                        ), respond]


        if self.state["current_intent"] == "push":
            return Action(
                    suggested_command='git push',
                    execute=False,
                    description=None,
                    confidence=confidence
                )

        if self.state["current_intent"] == "merge":
            merge_url = "{}/pulls".format(_github_url)

            source = None
            target = "master"

            for entity in response["entities"]:
                if entity["entity"] == "source": source = entity["value"]
                if entity["entity"] == "target": target = entity["value"]

            if not source: source = self.state["current_branch"]

            payload = { "title" : "Dummy PR from gitbot", 
                        "body"  : "Example from the `gitbot` screencast. This will not be merged.",
                        "head"  : source,
                        "base"  : target }

            ping_github = json.loads(self.gh_session.post(merge_url, json=payload).text)
            return Action(
                    suggested_command=NOOP_COMMAND,
                    execute=True,
                    description="Success. Created PR {}".format(ping_github["number"]),
                    confidence=confidence
                )

        if self.state["current_intent"] == "comment":

            idx = 0
            comment = None

            for entity in response["entities"]:
                if entity["entity"] == "id": idx = entity["value"]
                if entity["entity"] == "comment": comment = entity["value"]

            idx = command.split('<')[-1].split('>')[0]
            comment_url = "{}/issues/{}/comments".format(_github_url, idx)

            payload = { "body" : comment }

            ping_github = json.loads(self.gh_session.post(comment_url, json=payload).text)
            return Action(
                    suggested_command=NOOP_COMMAND,
                    execute=True,
                    description="Success",
                    confidence=confidence
                )
예제 #21
0
파일: howdoi.py 프로젝트: xzlin/clai
    def get_next_action(self, state: State) -> Action:

        logger.info(
            "================== In HowDoI Bot:get_next_action ========================"
        )
        logger.info(
            "State:\n\tCommand: {}\n\tError Code: {}\n\tStderr: {}".format(
                state.command, state.result_code, state.stderr))
        logger.info(
            "============================================================================"
        )

        # Invoke "howdoi" plugin only when a question is asked
        is_question = self.questionIdentifier.is_question(state.command)

        if not is_question:
            return Action(suggested_command=state.command, confidence=0.0)

        apis: OrderedDict = self.store.get_apis()
        helpWasFound = False
        for provider in apis:
            # We don't want to process the manpages provider... thats the provider
            # that we use to clarify results from other providers
            if provider == "manpages":
                logger.info(f"Skipping search provider 'manpages'")
                continue

            thisAPI: Provider = apis[provider]

            # Skip this provider if it isn't supported on the target OS
            if not thisAPI.can_run_on_this_os():
                logger.info(f"Skipping search provider '{provider}'")
                logger.info(
                    f"==> Excluded on platforms: {str(thisAPI.get_excludes())}"
                )
                continue  # Move to next provider in list

            logger.info(f"Processing search provider '{provider}'")

            if thisAPI.has_variants():
                logger.info(
                    f"==> Has search variants: {str(thisAPI.get_variants())}")
                variants: List = thisAPI.get_variants()
            else:
                logger.info(f"==> Has no search variants")
                variants: List = [None]

            # For each search variant supported by the current API, query
            # the data store to find the closest matching data.  If there are
            # no search variants (ie: the singleton variant case), the variants
            # list will only contain a single, Nonetype value.
            for variant in variants:

                if variant is not None:
                    logger.info(f"==> Searching variant '{variant}'")
                    data = self.store.search(state.command,
                                             service=provider,
                                             size=1,
                                             searchType=variant)
                else:
                    data = self.store.search(state.command,
                                             service=provider,
                                             size=1)

                if data:
                    logger.info(
                        f"==> Success!!! Found a result in the {thisAPI}")

                    # Find closest match b/w relevant data and manpages for unix
                    searchResult = thisAPI.extract_search_result(data)
                    manpages = self.store.search(searchResult,
                                                 service='manpages',
                                                 size=5)
                    if manpages:
                        logger.info("==> Success!!! found relevant manpages.")

                        command = manpages['commands'][-1]
                        confidence = manpages['dists'][-1]

                        logger.info("==> Command: {} \t Confidence:{}".format(
                            command, confidence))

                        # Set return data
                        suggested_command = "man {}".format(command)
                        description=Colorize() \
                            .emoji(Colorize.EMOJI_ROBOT).append(f"I did little bit of Internet searching for you, ") \
                            .append(f"and found this in the {thisAPI}:\n") \
                            .info() \
                            .append(thisAPI.get_printable_output(data)) \
                            .warning() \
                            .append("Do you want to try: man {}".format(command)) \
                            .to_console()

                        # Mark that help was indeed found
                        helpWasFound = True

                        # We've found help; no need to keep searching
                        break

            # If we found help, then break out of the outer loop as well
            if helpWasFound:
                break

        if not helpWasFound:
            logger.info("Failure: Unable to be helpful")
            logger.info(
                "============================================================================"
            )

            suggested_command = NOOP_COMMAND
            description=Colorize().emoji(Colorize.EMOJI_ROBOT) \
                .append(
                    f"Sorry. It looks like you have stumbled across a problem that even the Internet doesn't have answer to.\n") \
                .info() \
                .append(f"Have you tried turning it OFF and ON again. ;)") \
                .to_console()
            confidence = 0.0

        return Action(suggested_command=suggested_command,
                      description=description,
                      confidence=confidence)
예제 #22
0
파일: helpme.py 프로젝트: twinstar26/clai
    def post_execute(self, state: State) -> Action:

        logger.info(
            "=========================== In Helpme Bot:post_execute ==================================="
        )
        logger.info(
            "State:\n Command: {}\n Error Code: {}\n Stderr: {}".format(
                state.command, state.result_code, state.stderr))
        logger.info(
            "============================================================================"
        )
        if state.result_code != '0':
            # Query data store to find closest matching forum
            forum = self.store.search(state.stderr,
                                      service='stack_exchange',
                                      size=1)

            if forum:
                logger.info("Success!!! Found relevant forums.")

                # Find closes match b/w relevant forum and manpages for unix
                manpages = self.store.search(forum[0]['Answer'],
                                             service='manpages',
                                             size=5)
                if manpages:
                    logger.info("Success!!! found relevant manpages.")

                    command = manpages['commands'][-1]
                    confidence = manpages['dists'][-1]

                    # FIXME: Artificially boosted confidence
                    confidence = 1.0

                    logger.info("Command: {} \t Confidence:{}".format(
                        command, confidence))

                    return Action(
                        suggested_command="man {}".format(command),
                        description=Colorize(
                        ).emoji(Colorize.EMOJI_ROBOT).append(
                            f"I did little bit of internet searching for you.\n"
                        ).info().append("Post: {}\n".format(
                            forum[0]['Content'][:384] +
                            " ...")).append("Answer: {}\n".format(
                                forum[0]['Answer'][:256] +
                                " ...")).append("Link: {}\n\n".format(
                                    forum[0]['Url'])).warning().append(
                                        "Do you want to try: man {}".format(
                                            command)).to_console(),
                        confidence=confidence)
                else:
                    logger.info("Failure: Manpage search")
                    logger.info(
                        "============================================================================"
                    )

                    return Action(
                        suggested_command=NOOP_COMMAND,
                        description=Colorize(
                        ).emoji(Colorize.EMOJI_ROBOT).append(
                            f"Sorry. It looks like you have stumbled across a problem that even internet doesn't have answer to.\n"
                        ).info().append(
                            f"Have you tried turning it OFF and ON again. ;)").
                        to_console(),
                        confidence=0.0)
            else:
                logger.info("Failure: Forum search")
                logger.info(
                    "============================================================================"
                )
                return Action(
                    suggested_command=NOOP_COMMAND,
                    description=Colorize().emoji(Colorize.EMOJI_ROBOT).append(
                        f"Sorry. It looks like you have stumbled across a problem that even internet doesn't have answer to.\n"
                    ).warning().append(
                        f"Have you tried turning it OFF and ON again. ;)").
                    to_console(),
                    confidence=0.0)

        return Action(suggested_command=state.command)
예제 #23
0
def print_complete(text):
    print(Colorize().complete().append(text).to_console())
예제 #24
0
파일: howdoi.py 프로젝트: dowem/clai
    def get_next_action(self, state: State) -> Action:

        logger.info(
            "================== In HowDoI Bot:get_next_action ========================"
        )
        logger.info("User Command: {}".format(state.command))

        # Query data store to find closest matching forum
        forum = self.store.search(state.command,
                                  service='stack_exchange',
                                  size=1)

        if forum:
            # Find closes match b/w relevant forum for unix
            logger.info("Success!!! Found relevant forums.")

            # Find closes match b/w relevant forum and manpages for unix
            manpages = self.store.search(forum[0]['Answer'],
                                         service='manpages',
                                         size=5)
            if manpages:
                logger.info("Success!!! found relevant manpages.")

                command = manpages['commands'][-1]
                confidence = manpages['dists'][-1]

                logger.info("Command: {} \t Confidence:{}".format(
                    command, confidence))

                return Action(
                    suggested_command="man {}".format(command),
                    description=Colorize().emoji(Colorize.EMOJI_ROBOT).append(
                        f"I did little bit of internet searching for you.\n").
                    info().append("Post: {}\n".format(
                        forum[0]['Content'][:384] +
                        " ...")).append("Answer: {}\n".format(
                            forum[0]['Answer'][:256] + " ...")).append(
                                "Link: {}\n\n".format(
                                    forum[0]['Url'])).warning().append(
                                        "Do you want to try: man {}".format(
                                            command)).to_console(),
                    confidence=confidence)
            else:
                logger.info("Failure: Manpage search")
                logger.info(
                    "============================================================================"
                )

                return Action(
                    suggested_command=NOOP_COMMAND,
                    description=Colorize().emoji(Colorize.EMOJI_ROBOT).append(
                        f"Sorry. It looks like you have stumbled across a problem that even internet has not answer to.\n"
                    ).info().append(
                        f"Have you tried turning it OFF and ON again. ;)").
                    to_console(),
                    confidence=0.0)
        else:
            logger.info("Failure: Forum search")
            logger.info(
                "============================================================================"
            )
            return Action(
                suggested_command=NOOP_COMMAND,
                description=Colorize().emoji(Colorize.EMOJI_ROBOT).append(
                    f"Sorry. It looks like you have stumbled across a problem that even internet has not answer to.\n"
                ).warning().append(
                    f"Have you tried turning it OFF and ON again. ;)").
                to_console(),
                confidence=0.0)
예제 #25
0
파일: zmsgcode.py 프로젝트: xzlin/clai
    def post_execute(self, state: State) -> Action:

        logger.info("==================== In zMsgCode Bot:post_execute ============================")
        logger.info("State:\n\tCommand: {}\n\tError Code: {}\n\tStderr: {}".format(state.command,
                                                                                   state.result_code,
                                                                                   state.stderr))
        logger.info("============================================================================")
        
        if state.result_code == '0':
            return Action(suggested_command=state.command)
        
        stderr = state.stderr.strip()
        
        # This bot should be triggered if the message on STDERR resembles an
        # IBM z Systems message ID.  For example:
        #   FSUM8977 cp: source "test.txt" and target "test.txt" are identical
        #
        # If if the contents of stderr don't include a Z message code, move along
        matches = re.compile(REGEX_ZMSG).match(stderr)
        if matches is None:
            logger.info(f"No Z message ID found in '{stderr}'")
            return Action(suggested_command=state.command)
        
        logger.info(f"Analyzing error message '{matches[0]}'")
        msgid:str = matches[2]  # Isolate the message ID
        helpWasFound = False
        
        # If this message contains data which can be parsed by bpxmtext, then
        # try calling bpxmtext to get a string describing the error
        bpx_matches:List[str] = self.__search(matches[0], REGEX_BPX)
        if bpx_matches is not None:
            reason_code:str = bpx_matches[1]
            logger.info(f"==> Reason Code: {reason_code}")
            
            # Call bpmxtext to get info about that message
            result:CompletedProcess = subprocess.run(["bpxmtext", reason_code], stdout=subprocess.PIPE)
            if result.returncode == 0:
                messageText = result.stdout.decode('UTF8')
                
                # If bpmxtext's response is actually something useful, use it
                if self.__search(messageText, REGEX_BPX_BADANSWER) is None:
                    suggested_command=state.command
                    description=Colorize() \
                        .emoji(Colorize.EMOJI_ROBOT) \
                        .append(f"I asked bpxmtext about that message:\n") \
                        .info() \
                        .append(messageText) \
                        .warning() \
                        .to_console()
                    helpWasFound = True # Mark that help was indeed found
            
        # If this message wasn't one we could send to bpxmtext, or if bpxmtext
        # didn't return a meaningful message, try searching the KnowledgeCenter
        if not helpWasFound:
            kc_api:Provider = self.store.get_apis()['ibm_kc']
            if kc_api is not None and kc_api.can_run_on_this_os(): 
                data = self.store.search(msgid, service='ibm_kc', size=1) 
                if data:
                    logger.info(f"==> Success!!! Found information for msgid {msgid}")
                    suggested_command=state.command
                    description=Colorize() \
                        .emoji(Colorize.EMOJI_ROBOT) \
                        .append(
                            f"I looked up {msgid} in the IBM KnowledgeCenter for you:\n") \
                        .info() \
                        .append(kc_api.get_printable_output(data)) \
                        .warning() \
                        .to_console()
                    helpWasFound = True # Mark that help was indeed found
            
        if not helpWasFound:
            logger.info("Failure: Unable to be helpful")
            logger.info("============================================================================")
            
            suggested_command=NOOP_COMMAND
            description=Colorize() \
                .emoji(Colorize.EMOJI_ROBOT) \
                .append(
                    f"I couldn't find any help for message code '{msgid}'\n") \
                .info() \
                .to_console()
                
        return Action(suggested_command=suggested_command,
                      description=description,
                      confidence=1.0)