diff --git a/pyinfra_cli/util.py b/pyinfra_cli/util.py
index 95ea7b62e..111dc96f1 100644
--- a/pyinfra_cli/util.py
+++ b/pyinfra_cli/util.py
@@ -15,6 +15,7 @@
 import gevent
 
 from pyinfra import logger, state
+from pyinfra.api import FactBase
 from pyinfra.api.command import PyinfraCommand
 from pyinfra.api.exceptions import PyinfraError
 from pyinfra.api.host import HostData
@@ -153,6 +154,37 @@ def parse_cli_arg(arg):
     return arg
 
 
+def get_module_available_actions(module: ModuleType) -> dict[str, list[str]]:
+    """
+    Lists operations and facts available in a module.
+
+    Args:
+        module (ModuleType): The module to inspect.
+
+    Returns:
+        dict: A dictionary with keys 'operations' and 'facts', each containing a list of names.
+    """
+    available_actions: dict[str, list[str]] = {
+        "operations": [],
+        "facts": [],
+    }
+
+    for name, obj in module.__dict__.items():
+        # Check if it's a decorated operation
+        if callable(obj) and getattr(obj, "is_idempotent", None) is not None:
+            available_actions["operations"].append(name)
+
+        # Check if it's a FactBase subclass
+        elif (
+            isinstance(obj, type)
+            and issubclass(obj, FactBase)
+            and obj.__module__ == module.__name__
+        ):
+            available_actions["facts"].append(name)
+
+    return available_actions
+
+
 def try_import_module_attribute(path, prefix=None, raise_for_none=True):
     if ":" in path:
         # Allow a.module.name:function syntax
@@ -189,7 +221,36 @@ def try_import_module_attribute(path, prefix=None, raise_for_none=True):
     attr = getattr(module, attr_name, None)
     if attr is None:
         if raise_for_none:
-            raise CliError(f"No such attribute in module {possible_modules[0]}: {attr_name}")
+            extra_info = []
+            module_name = getattr(module, "__name__", str(module))
+
+            if prefix == "pyinfra.operations":
+                # List classes of type OperationMeta
+                available_operations = get_module_available_actions(module)["operations"]
+                if available_operations:
+                    extra_info.append(
+                        f"Available operations are: {', '.join(available_operations)}"
+                    )
+                else:
+                    extra_info.append(
+                        "No operations found. Maybe you have a file or folder named "
+                        f"`{str(module_name)}` in the current folder ?"
+                    )
+
+            elif prefix == "pyinfra.facts":
+                # List classes of type FactBase
+                available_facts = get_module_available_actions(module)["facts"]
+                if available_facts:
+                    extra_info.append(f"Available facts are: {', '.join(available_facts)}")
+                else:
+                    extra_info.append(
+                        "No facts found. Maybe you have a file or folder named "
+                        f"`{str(module_name)}` in the current folder ?"
+                    )
+
+            message = [f"No such attribute in module {possible_modules[0]}: {attr_name}"]
+
+            raise CliError("\n".join(message + extra_info))
         return
 
     return attr
diff --git a/tests/test_cli/test_cli_exceptions.py b/tests/test_cli/test_cli_exceptions.py
index 7cac5f15a..c1c309761 100644
--- a/tests/test_cli/test_cli_exceptions.py
+++ b/tests/test_cli/test_cli_exceptions.py
@@ -1,3 +1,4 @@
+import re
 import sys
 from os import path
 from unittest import TestCase
@@ -21,7 +22,24 @@ def setUpClass(cls):
     def assert_cli_exception(self, args, message):
         result = self.runner.invoke(cli, args, standalone_mode=False)
         self.assertIsInstance(result.exception, CliError)
-        assert getattr(result.exception, "message") == message
+
+        if isinstance(message, str):
+            message = [message]
+
+        for part in message:
+
+            # Test if the string is a regex
+            is_regex = False
+            try:
+                re.compile(part)
+                is_regex = True
+            except re.error:
+                pass
+
+            if is_regex:
+                assert re.search(part, result.exception.message, re.MULTILINE)
+            else:
+                assert part == result.exception.message
 
     def test_bad_deploy_file(self):
         self.assert_cli_exception(
@@ -44,7 +62,12 @@ def test_no_fact_module(self):
     def test_no_fact_cls(self):
         self.assert_cli_exception(
             ["my-server.net", "fact", "server.NotAFact"],
-            "No such attribute in module server: NotAFact",
+            [
+                r"^No such attribute in module server: NotAFact.*",
+                r".*Available facts are: .*",
+                r".* User,.*",
+                r".* Os,",
+            ],
         )
 
 
diff --git a/tests/test_cli/test_cli_util.py b/tests/test_cli/test_cli_util.py
index 776510f14..ec7f6b484 100644
--- a/tests/test_cli/test_cli_util.py
+++ b/tests/test_cli/test_cli_util.py
@@ -1,4 +1,5 @@
 import os
+import re
 import sys
 from datetime import datetime
 from io import StringIO
@@ -32,11 +33,17 @@ def test_setup_no_module(self):
             get_func_and_args(("no.op",))
         assert context.exception.message == "No such module: no"
 
-    def test_setup_no_op(self):
+    def test_setup_not_exists_op(self):
         with self.assertRaises(CliError) as context:
-            get_func_and_args(("server.no",))
-
-        assert context.exception.message == "No such attribute in module server: no"
+            get_func_and_args(("server.not_exists",))
+
+        for part in [
+            r"^No such attribute in module server: not_exists$",
+            r"Available operations are: .*",
+            r".* modprobe, .*",
+            r".* mount, .*",
+        ]:
+            assert re.search(part, context.exception.message, re.MULTILINE)
 
     def test_setup_op_and_args(self):
         commands = ("pyinfra.operations.server.user", "one", "two", "hello=world")