diff --git a/NEWS.rst b/NEWS.rst
index eb5c3b3f..1136f216 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -14,6 +14,9 @@ Breaking Changes
   Forms like `#*word` will attempt to dispatch a macro named `*word`;
   to unpack a symbol named `word`, write `#* word` (note the space).
 * Reader macro names are no longer mangled.
+* `hy2py`'s recursive mode now expects a module name as input, not any
+  old directory. You must be in the parent directory of the module
+  directory.
 
 Bug Fixes
 ------------------------------
@@ -22,6 +25,8 @@ Bug Fixes
 * Fixed some bugs with traceback pointing.
 * Fixed some bugs with escaping in bracket f-strings
 * The parser no longer looks for shebangs in the REPL or `hy -c`.
+* `require` with relative module names should now work correctly with
+  `hy -m`, as well as `hy2py`'s recursive mode.
 
 New Features
 ------------------------------
diff --git a/docs/cli.rst b/docs/cli.rst
index f40c18f5..0f8227a0 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -44,7 +44,7 @@ for a complete list of options and :py:ref:`Python's documentation
 hy2py
 -----
 
-``hy2py`` is a program to convert Hy source code into Python source code. Use ``hy2py --help`` for usage instructions. It can take its input from standard input, from a filename, or folder name provided as a command-line argument. If it is a folder, the output parameter (--output/-o) must be provided. When the output parameter is provided, the output will be written into the folder or file, otherwise the result is written to standard output.
+``hy2py`` is a program to convert Hy source code into Python source code. Use ``hy2py --help`` for usage instructions. It can take its input from standard input, or from a file or module name provided as a command-line argument. In the case of a module name, the current working directory should be the parent directory of that module, and the output parameter (``--output/-o``) is required. When the output parameter is provided, the output will be written into the folder or file. Otherwise, the result is written to standard output.
 
     .. warning::
        ``hy2py`` can execute arbitrary code. Don't give it untrusted input.
diff --git a/hy/cmdline.py b/hy/cmdline.py
index dca3febb..73e02eac 100644
--- a/hy/cmdline.py
+++ b/hy/cmdline.py
@@ -5,6 +5,7 @@ import io
 import os
 import platform
 import py_compile
+import re
 import runpy
 import sys
 from contextlib import nullcontext
@@ -332,7 +333,9 @@ def hyc_main():
 
 
 def hy2py_worker(source, options, filename, output_filepath=None):
+    source_path = None
     if isinstance(source, Path):
+        source_path = source
         source = source.read_text(encoding="UTF-8")
 
     if not output_filepath and options.output:
@@ -358,7 +361,13 @@ def hy2py_worker(source, options, filename, output_filepath=None):
         hst.filename = filename
 
         with filtered_hy_exceptions():
-            _ast = hy_compile(hst, "__main__", filename=filename, source=source)
+            _ast = hy_compile(
+                 hst,
+                 re.sub(r'\.hy$', '', '.'.join(source_path.parts))
+                     if source_path
+                     else '__main__',
+                 filename=filename,
+                 source=source)
 
         if options.with_source:
             print()
@@ -385,7 +394,7 @@ def hy2py_main():
         "FILE",
         type=str,
         nargs="?",
-        help='Input Hy code (can be file or directory) (use STDIN if "-" or '
+        help='Input Hy code (can be file or module) (use STDIN if "-" or '
         "not provided)",
     )
     parser.add_argument(
diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py
index eb296ffd..4d42a003 100644
--- a/hy/core/result_macros.py
+++ b/hy/core/result_macros.py
@@ -1783,6 +1783,8 @@ def compile_require(compiler, expr, root, entries):
                         dotted("hy.macros.require"),
                         String(module_name),
                         Symbol("None"),
+                        Keyword("target_module_name"),
+                        String(compiler.module.__name__),
                         Keyword("assignments"),
                         (
                             String("EXPORTS")
diff --git a/hy/macros.py b/hy/macros.py
index 3f159a21..d684310f 100644
--- a/hy/macros.py
+++ b/hy/macros.py
@@ -197,7 +197,7 @@ def enable_readers(module, reader, names):
         reader.reader_macros[name] = namespace["_hy_reader_macros"][name]
 
 
-def require(source_module, target_module, assignments, prefix=""):
+def require(source_module, target_module, assignments, prefix="", target_module_name=None):
     """Load macros from one module into the namespace of another.
 
     This function is called from the macro also named `require`.
@@ -213,6 +213,7 @@ def require(source_module, target_module, assignments, prefix=""):
         prefix (str): If nonempty, its value is prepended to the name of each imported macro.
             This allows one to emulate namespaced macros, like "mymacromodule.mymacro",
             which looks like an attribute of a module. Defaults to ""
+        target_module_name: If true, overrides the apparent name of `target_module`.
 
     Returns:
         bool: Whether or not macros were actually transferred.
@@ -230,7 +231,8 @@ def require(source_module, target_module, assignments, prefix=""):
         return False
 
     if not inspect.ismodule(source_module):
-        source_module = import_module_from_string(source_module, target_module)
+        source_module = import_module_from_string(source_module,
+           target_module_name or target_module)
 
     source_macros = source_module.__dict__.setdefault("_hy_macros", {})
     source_exports = getattr(

diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
index 6cb4c421..4a743e23 100644
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -1,5 +1,3 @@
-# fmt: off
-
 import ast
 from textwrap import dedent
 
diff --git a/tests/test_bin.py b/tests/test_bin.py
index 32e3c84f..b3168243 100644
--- a/tests/test_bin.py
+++ b/tests/test_bin.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# fmt: off
 
 import builtins
 import os
@@ -742,26 +741,53 @@ def test_assert(tmp_path, monkeypatch):
             assert ("bye" in err) == show_msg
 
 
-def test_hy2py_recursive(tmp_path):
-    (tmp_path / 'hy').mkdir()
-    (tmp_path / "hy/first.hy").write_text("""
-        (import folder.second [a b])
+def test_hy2py_recursive(monkeypatch, tmp_path):
+    (tmp_path / 'foo').mkdir()
+    (tmp_path / 'foo/__init__.py').touch()
+    (tmp_path / "foo/first.hy").write_text("""
+        (import foo.folder.second [a b])
         (print a)
         (print b)""")
-    (tmp_path / "hy/folder").mkdir()
-    (tmp_path / "hy/folder/second.hy").write_text("""
+    (tmp_path / "foo/folder").mkdir()
+    (tmp_path / "foo/folder/__init__.py").touch()
+    (tmp_path / "foo/folder/second.hy").write_text("""
         (setv a 1)
         (setv b "hello world")""")
 
-    _, err = run_cmd(["hy2py", (tmp_path / 'hy')], expect=1)
+    monkeypatch.chdir(tmp_path)
+
+    _, err = run_cmd("hy2py foo", expect=1)
     assert "ValueError" in err
 
-    run_cmd(["hy2py",
-        (tmp_path / 'hy'),
-        "--output", (tmp_path / 'py')])
-    assert set((tmp_path / 'py').rglob('*')) == {
-        tmp_path / 'py' / p
+    run_cmd("hy2py foo --output bar")
+    assert set((tmp_path / 'bar').rglob('*')) == {
+        tmp_path / 'bar' / p
         for p in ('first.py', 'folder', 'folder/second.py')}
 
-    output, _ = run_cmd("python3 first.py", cwd = tmp_path / 'py')
+    output, _ = run_cmd("python3 first.py", cwd = tmp_path / 'bar')
     assert output == "1\nhello world\n"
+
+
+@pytest.mark.parametrize('case', ['hy -m', 'hy2py'])
+def test_relative_require(case, monkeypatch, tmp_path):
+    # https://github.com/hylang/hy/issues/2204
+
+    (tmp_path / 'pkg').mkdir()
+    (tmp_path / 'pkg' / '__init__.py').touch()
+    (tmp_path / 'pkg' / 'a.hy').write_text('''
+        (defmacro m []
+          '(setv x (.upper "hello")))''')
+    (tmp_path / 'pkg' / 'b.hy').write_text('''
+        (require .a [m])
+        (m)
+        (print x)''')
+    monkeypatch.chdir(tmp_path)
+
+    if case == 'hy -m':
+        output, _ = run_cmd('hy -m pkg.b')
+    elif case == 'hy2py':
+        run_cmd('hy2py pkg -o out')
+        (tmp_path / 'out' / '__init__.py').touch()
+        output, _ = run_cmd('python3 -m out.b')
+
+    assert 'HELLO' in output

`hy2py` and relative require
here is my module
```
.
├── __init__.py
├── args.hy
├── config.hy
├── log.hy
├── macros.hy
├── main.hy
├── type_helpers.hy
└── utils.hy
```

when i try to compile `hy2py args.hy` it raises:
```
hy.errors.HyMacroExpansionError:
  File "args.hy", line 10
    (require .macros *)
    ^-----------------^
expanding macro require
  TypeError: the 'package' argument is required to perform a relative import for '.macros'
```

if i remove dot
```hy
(require macros *)
```
then it works with `hy2py` but breaks running module with entry point
tests/test_bin.py::test_relative_require[hy tests/test_bin.py::test_relative_require[hy2py]
tests/compilers/test_ast.py::test_ast_bad_type tests/compilers/test_ast.py::test_empty_expr tests/compilers/test_ast.py::test_dot_unpacking tests/compilers/test_ast.py::test_ast_bad_if tests/compilers/test_ast.py::test_ast_valid_if tests/compilers/test_ast.py::test_ast_bad_while tests/compilers/test_ast.py::test_ast_good_do tests/compilers/test_ast.py::test_ast_good_raise tests/compilers/test_ast.py::test_ast_raise_from tests/compilers/test_ast.py::test_ast_bad_raise tests/compilers/test_ast.py::test_ast_good_try tests/compilers/test_ast.py::test_ast_bad_try tests/compilers/test_ast.py::test_ast_good_except tests/compilers/test_ast.py::test_ast_bad_except tests/compilers/test_ast.py::test_ast_good_assert tests/compilers/test_ast.py::test_ast_bad_assert tests/compilers/test_ast.py::test_ast_good_global tests/compilers/test_ast.py::test_ast_bad_global tests/compilers/test_ast.py::test_ast_good_nonlocal tests/compilers/test_ast.py::test_ast_bad_nonlocal tests/compilers/test_ast.py::test_ast_good_defclass tests/compilers/test_ast.py::test_ast_good_defclass_with_metaclass tests/compilers/test_ast.py::test_ast_bad_defclass tests/compilers/test_ast.py::test_ast_good_lambda tests/compilers/test_ast.py::test_ast_bad_lambda tests/compilers/test_ast.py::test_ast_good_yield tests/compilers/test_ast.py::test_ast_bad_yield tests/compilers/test_ast.py::test_ast_import_mangle_dotted tests/compilers/test_ast.py::test_ast_good_import_from tests/compilers/test_ast.py::test_ast_require tests/compilers/test_ast.py::test_ast_import_require_dotted tests/compilers/test_ast.py::test_ast_multi_require tests/compilers/test_ast.py::test_ast_good_get tests/compilers/test_ast.py::test_ast_bad_get tests/compilers/test_ast.py::test_ast_good_cut tests/compilers/test_ast.py::test_ast_bad_cut tests/compilers/test_ast.py::test_ast_bad_with tests/compilers/test_ast.py::test_ast_valid_while tests/compilers/test_ast.py::test_ast_valid_for tests/compilers/test_ast.py::test_nullary_break_continue tests/compilers/test_ast.py::test_ast_expression_basics tests/compilers/test_ast.py::test_ast_anon_fns_basics tests/compilers/test_ast.py::test_ast_lambda_lists tests/compilers/test_ast.py::test_ast_print tests/compilers/test_ast.py::test_ast_tuple tests/compilers/test_ast.py::test_lambda_list_keywords_rest tests/compilers/test_ast.py::test_lambda_list_keywords_kwargs tests/compilers/test_ast.py::test_lambda_list_keywords_kwonly tests/compilers/test_ast.py::test_lambda_list_keywords_mixed tests/compilers/test_ast.py::test_missing_keyword_argument_value tests/compilers/test_ast.py::test_ast_unicode_strings tests/compilers/test_ast.py::test_ast_unicode_vs_bytes tests/compilers/test_ast.py::test_format_string tests/compilers/test_ast.py::test_ast_bracket_string tests/compilers/test_ast.py::test_literal_newlines tests/compilers/test_ast.py::test_compile_error tests/compilers/test_ast.py::test_for_compile_error tests/compilers/test_ast.py::test_attribute_access tests/compilers/test_ast.py::test_misplaced_dots tests/compilers/test_ast.py::test_bad_setv tests/compilers/test_ast.py::test_defn tests/compilers/test_ast.py::test_setv_builtins tests/compilers/test_ast.py::test_top_level_unquote tests/compilers/test_ast.py::test_bad_exception tests/compilers/test_ast.py::test_lots_of_comment_lines tests/compilers/test_ast.py::test_compiler_macro_tag_try tests/compilers/test_ast.py::test_ast_good_yield_from tests/compilers/test_ast.py::test_ast_bad_yield_from tests/compilers/test_ast.py::test_eval_generator_with_return tests/compilers/test_ast.py::test_futures_imports tests/compilers/test_ast.py::test_py tests/compilers/test_ast.py::test_pys tests/compilers/test_ast.py::test_models_accessible tests/compilers/test_ast.py::test_module_prelude tests/compilers/test_ast.py::test_pragma tests/test_bin.py::test_simple tests/test_bin.py::test_stdin tests/test_bin.py::test_stdin_multiline tests/test_bin.py::test_history tests/test_bin.py::test_stdin_comments tests/test_bin.py::test_stdin_assignment tests/test_bin.py::test_multi_setv tests/test_bin.py::test_stdin_error_underline_alignment tests/test_bin.py::test_stdin_except_do tests/test_bin.py::test_stdin_unlocatable_hytypeerror tests/test_bin.py::test_error_parts_length tests/test_bin.py::test_syntax_errors tests/test_bin.py::test_stdin_bad_repr tests/test_bin.py::test_stdin_py_repr tests/test_bin.py::test_mangle_m tests/test_bin.py::test_ignore_python_env tests/test_bin.py::test_cmd tests/test_bin.py::test_icmd tests/test_bin.py::test_icmd_file tests/test_bin.py::test_icmd_and_spy tests/test_bin.py::test_empty_file tests/test_bin.py::test_missing_file tests/test_bin.py::test_file_with_args tests/test_bin.py::test_hyc tests/test_bin.py::test_hyc_missing_file tests/test_bin.py::test_builtins tests/test_bin.py::test_no_main tests/test_bin.py::test_byte_compile[cmd_fmt0-normal] tests/test_bin.py::test_byte_compile[cmd_fmt0-prevent_by_force] tests/test_bin.py::test_byte_compile[cmd_fmt0-prevent_by_env] tests/test_bin.py::test_byte_compile[cmd_fmt0-prevent_by_option] tests/test_bin.py::test_byte_compile[cmd_fmt1-normal] tests/test_bin.py::test_byte_compile[cmd_fmt1-prevent_by_force] tests/test_bin.py::test_byte_compile[cmd_fmt1-prevent_by_env] tests/test_bin.py::test_byte_compile[cmd_fmt1-prevent_by_option] tests/test_bin.py::test_byte_compile[cmd_fmt2-normal] tests/test_bin.py::test_byte_compile[cmd_fmt2-prevent_by_force] tests/test_bin.py::test_byte_compile[cmd_fmt2-prevent_by_env] tests/test_bin.py::test_byte_compile[cmd_fmt2-prevent_by_option] tests/test_bin.py::test_module_main_file tests/test_bin.py::test_file_main_file tests/test_bin.py::test_file_sys_path tests/test_bin.py::test_module_no_main tests/test_bin.py::test_sys_executable tests/test_bin.py::test_file_no_extension tests/test_bin.py::test_circular_macro_require tests/test_bin.py::test_macro_require tests/test_bin.py::test_tracebacks tests/test_bin.py::test_traceback_shebang tests/test_bin.py::test_hystartup tests/test_bin.py::test_output_buffering tests/test_bin.py::test_uufileuu tests/test_bin.py::test_assert tests/test_bin.py::test_hy2py_recursive