diff --git a/src/godotiq/__main__.py b/src/godotiq/__main__.py
index d7bb079..39f1800 100644
--- a/src/godotiq/__main__.py
+++ b/src/godotiq/__main__.py
@@ -1,2 +1,3 @@
-from godotiq.server import main
-main()
+from godotiq.cli import cli_main
+
+cli_main()
diff --git a/src/godotiq/cli.py b/src/godotiq/cli.py
new file mode 100644
index 0000000..c0e3ac0
--- /dev/null
+++ b/src/godotiq/cli.py
@@ -0,0 +1,65 @@
+"""GodotIQ CLI — argument parsing and subcommand dispatch."""
+
+from __future__ import annotations
+
+import argparse
+import sys
+from pathlib import Path
+
+
+def cli_main() -> None:
+    """Parse arguments and dispatch to the appropriate handler."""
+    from godotiq import __version__
+
+    parser = argparse.ArgumentParser(
+        prog="godotiq",
+        description="GodotIQ — The definitive MCP for AI-assisted Godot development.",
+    )
+    parser.add_argument(
+        "--version", action="version", version=f"godotiq {__version__}"
+    )
+
+    subparsers = parser.add_subparsers(dest="command")
+
+    install_parser = subparsers.add_parser(
+        "install-addon", help="Copy GodotIQ addon files to a Godot project"
+    )
+    install_parser.add_argument("path", help="Path to Godot project root")
+    install_parser.add_argument(
+        "--dry-run",
+        action="store_true",
+        help="Preview what would be copied without writing files",
+    )
+
+    args = parser.parse_args()
+
+    if args.command is None:
+        _serve()
+    elif args.command == "install-addon":
+        install_addon(args.path, dry_run=args.dry_run)
+
+
+def _serve() -> None:
+    """Start the MCP server (lazy import to avoid loading all tools for other subcommands)."""
+    from godotiq.server import main
+
+    main()
+
+
+def install_addon(target_dir: str, dry_run: bool = False) -> None:
+    """Copy addon files and GODOTIQ_RULES.md to a Godot project.
+
+    Full implementation in section-04.
+    """
+    target = Path(target_dir).resolve()
+    if not target.is_dir():
+        print(f"Error: directory does not exist: {target}", file=sys.stderr)
+        sys.exit(1)
+    if not (target / "project.godot").exists():
+        print(
+            f"Error: {target} does not appear to be a Godot project (no project.godot found)",
+            file=sys.stderr,
+        )
+        sys.exit(1)
+    print("install-addon: not yet implemented (section-04)", file=sys.stderr)
+    sys.exit(1)
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644
index 0000000..15b250b
--- /dev/null
+++ b/tests/test_cli.py
@@ -0,0 +1,138 @@
+"""Tests for godotiq.cli argument parsing and dispatch."""
+
+import subprocess
+import sys
+
+import pytest
+
+
+class TestCliParsing:
+    """Test argparse routing in cli.py."""
+
+    def test_no_args_defaults_to_serve(self, monkeypatch):
+        """When invoked with no arguments, cli_main should dispatch to _serve."""
+        served = []
+        monkeypatch.setattr("godotiq.cli._serve", lambda: served.append(True))
+        monkeypatch.setattr("sys.argv", ["godotiq"])
+        from godotiq.cli import cli_main
+        cli_main()
+        assert served == [True]
+
+    def test_version_flag(self, capsys):
+        """godotiq --version should print the version and exit."""
+        import sys as _sys
+        _sys.argv = ["godotiq", "--version"]
+        from godotiq.cli import cli_main
+        with pytest.raises(SystemExit) as exc_info:
+            cli_main()
+        assert exc_info.value.code == 0
+        captured = capsys.readouterr()
+        from godotiq import __version__
+        assert __version__ in captured.out
+
+    def test_help_flag(self, capsys):
+        """godotiq --help should print help text and exit."""
+        import sys as _sys
+        _sys.argv = ["godotiq", "--help"]
+        from godotiq.cli import cli_main
+        with pytest.raises(SystemExit) as exc_info:
+            cli_main()
+        assert exc_info.value.code == 0
+        captured = capsys.readouterr()
+        assert "install-addon" in captured.out
+
+    def test_install_addon_requires_path(self, monkeypatch):
+        """install-addon with no path argument should fail."""
+        monkeypatch.setattr("sys.argv", ["godotiq", "install-addon"])
+        from godotiq.cli import cli_main
+        with pytest.raises(SystemExit) as exc_info:
+            cli_main()
+        assert exc_info.value.code != 0
+
+    def test_install_addon_accepts_path(self, monkeypatch, tmp_path):
+        """install-addon with a path argument should be accepted."""
+        called_with = {}
+        def mock_install(target_dir, dry_run=False):
+            called_with["target_dir"] = target_dir
+            called_with["dry_run"] = dry_run
+        monkeypatch.setattr("godotiq.cli.install_addon", mock_install)
+        monkeypatch.setattr("sys.argv", ["godotiq", "install-addon", str(tmp_path)])
+        from godotiq.cli import cli_main
+        cli_main()
+        assert called_with["target_dir"] == str(tmp_path)
+        assert called_with["dry_run"] is False
+
+    def test_install_addon_dry_run_flag(self, monkeypatch, tmp_path):
+        """install-addon --dry-run should set dry_run=True."""
+        called_with = {}
+        def mock_install(target_dir, dry_run=False):
+            called_with["dry_run"] = dry_run
+        monkeypatch.setattr("godotiq.cli.install_addon", mock_install)
+        monkeypatch.setattr("sys.argv", ["godotiq", "install-addon", "--dry-run", str(tmp_path)])
+        from godotiq.cli import cli_main
+        cli_main()
+        assert called_with["dry_run"] is True
+
+    def test_unknown_subcommand_errors(self, monkeypatch):
+        """Unknown subcommand should produce an error exit."""
+        monkeypatch.setattr("sys.argv", ["godotiq", "nonexistent"])
+        from godotiq.cli import cli_main
+        with pytest.raises(SystemExit) as exc_info:
+            cli_main()
+        assert exc_info.value.code != 0
+
+
+class TestLazyImports:
+    """Verify cli.py does not eagerly import server.py."""
+
+    def test_cli_import_does_not_import_server(self):
+        """Importing godotiq.cli should not trigger import of godotiq.server."""
+        result = subprocess.run(
+            [sys.executable, "-c",
+             "import godotiq.cli; import sys; print('godotiq.server' not in sys.modules)"],
+            capture_output=True, text=True, timeout=10,
+        )
+        assert result.returncode == 0
+        assert "True" in result.stdout
+
+    def test_install_addon_path_does_not_import_server(self, tmp_path):
+        """The install-addon code path should not import godotiq.server."""
+        script = tmp_path / "check_lazy.py"
+        script.write_text(
+            "import sys\n"
+            "sys.argv = ['godotiq', 'install-addon', '/tmp/fake']\n"
+            "from godotiq.cli import cli_main\n"
+            "try:\n"
+            "    cli_main()\n"
+            "except SystemExit:\n"
+            "    pass\n"
+            "print('godotiq.server' not in sys.modules)\n"
+        )
+        result = subprocess.run(
+            [sys.executable, str(script)],
+            capture_output=True, text=True, timeout=10,
+        )
+        assert "True" in result.stdout
+
+
+class TestMainModule:
+    """Test python -m godotiq routing."""
+
+    def test_python_m_version(self):
+        """python -m godotiq --version should print the version."""
+        result = subprocess.run(
+            [sys.executable, "-m", "godotiq", "--version"],
+            capture_output=True, text=True, timeout=10,
+        )
+        assert result.returncode == 0
+        from godotiq import __version__
+        assert __version__ in result.stdout
+
+    def test_python_m_help(self):
+        """python -m godotiq --help should succeed."""
+        result = subprocess.run(
+            [sys.executable, "-m", "godotiq", "--help"],
+            capture_output=True, text=True, timeout=10,
+        )
+        assert result.returncode == 0
+        assert "install-addon" in result.stdout
